需求

将商品表数据全量更新至ES索引
商品索引支持Suggester自动补全,支持过滤商品enable和delete_status状态,只筛选启用且未删除的商品
Suggester与普通搜索区别:ES将Suggest机器依赖的字段放在堆内存,实现近实时的搜索提示功能

es安装ik分词插件

./bin/elasticsearch-plugin install https://github.com/medcl/elasticsearch-analysis-ik/releases/download/v7.7.0/elasticsearch-analysis-ik-7.7.0.zip

配置logstash配置文件

input {
    stdin {
    }
    jdbc {
      # 连接的数据库地址和哪一个数据库,指定编码格式,禁用SSL协议,设定自动重连
      jdbc_connection_string => "jdbc:mysql://{你的MySQL}:3306/shop-server?characterEncoding=UTF-8&useSSL=false&autoReconnect=true"
      # 你的账户密码
      jdbc_user => "{你的用户}"
      jdbc_password => "{你的密码}"
      # 连接数据库的驱动包,建议使用绝对地址
      jdbc_driver_library => "/data/logstash-6.4.0/bin/mysql/mysql-connector-java-5.1.22-bin.jar"
      # 这是不用动就好
      jdbc_driver_class => "com.mysql.jdbc.Driver"
      jdbc_paging_enabled => "true"
      jdbc_page_size => "2000"

          #处理中文乱码问题
      codec => plain { charset => "UTF-8"}

      #使用其它字段追踪,而不是用时间
      use_column_value => true
      #追踪的字段
      tracking_column => app_goods_id
      record_last_run => true
        statement => "SELECT
        g.app_goods_id,
        g.goods_id,
        g.goods_name,
        g.goods_name AS suggest,
        g.collect_count,
        g.sale,
        g.alone_price,
        g.enable,
        g.delete_status,
         (SELECT GROUP_CONCAT( goods_type_id ) FROM db_goods_type_link WHERE goods_id = g.goods_Id ) AS goods_type_id,
        CASE
                WHEN g.alone_price > 301 THEN
                100
                WHEN g.alone_price > 101 THEN
                300
                WHEN g.alone_price > 51 THEN
                500
                WHEN g.alone_price > 0 THEN
                400 ELSE 0
        END price_score,
        CASE
                b.brand_level
                WHEN 1 THEN
                500
                WHEN 2 THEN
                300
                WHEN 3 THEN
                100
                WHEN 4 THEN
                0 ELSE 0
        END brand_score
        FROM
        db_app_goods g
        LEFT JOIN db_brand b ON g.brand_id = b.brand_id
        LIMIT 2000"

      #上一个sql_last_value值的存放文件路径, 必须要在文件中指定字段的初始值
      last_run_metadata_path => "/data/logstash-6.4.0/bin/mysql/goods.log"

      jdbc_default_timezone => "Asia/Shanghai"

      #statement_filepath => "mysql/jdbc.sql"


      #是否清除 last_run_metadata_path 的记录,如果为真那么每次都相当于从头开始查询所有的数据库记录
      clean_run => false

      # 这是控制定时的,重复执行导入任务的时间间隔,第一位是分钟
      schedule => "* */1 * * *"
      type => "jdbc"
    }
}


filter {
    json {
        source => "message"
        remove_field => ["message"]
    }
}


output {
    elasticsearch {
        # 要导入到的Elasticsearch所在的主机
        hosts => "127.0.0.1:9200"
        # 要导入到的Elasticsearch的索引的名称
        index => "goods"
        # 类型名称(类似数据库表名)
        #document_type => "appgood"
        # 主键名称(类似数据库主键)
        document_id => "%{app_goods_id}"
        # es 账号
        user => {你的ES用户}
        password => {你的ES密码}
        # 这里配置为当前logstash的相对路径,该文件配置了输出的Mapping
        template => "mysql/goods_mapping.json"
        template_name => "goods"
        template_overwrite => true

    }

    stdout {
        # JSON格式输出
        codec => json_lines
    }
}

创建商品索引映射goods_mapping.json

{
        "template": "goods",
        "settings": {
                "index.refresh_interval": "1s"
        },
        "index_patterns": ["goods"],
        "mappings": {
                "properties": {
                        "suggest": {
                                "type": "completion",
                                "analyzer": "ik_smart",
                                "search_analyzer": "ik_smart",
                                "contexts": [{
                                                "name": "enable_cat",
                                                "type": "category",
                                                "path": "enable"
                                        },
                                        {
                                                "name": "delete_status_cat",
                                                "type": "category",
                                                "path": "delete_status"
                                        }
                                ]
                        },
                        "goods_name": {
                                "type": "text",
                                "analyzer": "ik_max_word",
                                "search_analyzer": "ik_smart"
                        },
                        "goods_type_id": {
                                "type": "keyword"
                        },
                        "app_goods_id": {
                                "type": "long"
                        },
                        "goods_id": {
                                "type": "long"
                        },
                        "collect_count": {
                                "type": "integer"
                        },
                        "sale": {
                                "type": "integer"
                        },
                        "alone_price": {
                                "type": "double"
                        },
                        "brand_score": {
                                "type": "integer"
                        },
                        "enable": {
                                "type": "keyword"
                        },
                        "delete_status" :{
                                "type": "keyword"
                        }
                }
        }
}

使用logstash -f {goods配置文件}启动logstash,logstash将自动从数据库查询并以Mapping创建索引

查看索引是否映射成功
GET goods_dev/_mapping
结果:

{
  "goods_dev" : {
    "mappings" : {
      "properties" : {
        "@timestamp" : {
          "type" : "date"
        },
        "@version" : {
          "type" : "text",
          "fields" : {
            "keyword" : {
              "type" : "keyword",
              "ignore_above" : 256
            }
          }
        },
        "alone_price" : {
          "type" : "double"
        },
        "app_goods_id" : {
          "type" : "long"
        },
        "brand_score" : {
          "type" : "integer"
        },
        "collect_count" : {
          "type" : "integer"
        },
        "delete_status" : {
          "type" : "keyword"
        },
        "enable" : {
          "type" : "keyword"
        },
        "goods_id" : {
          "type" : "long"
        },
        "goods_name" : {
          "type" : "text",
          "analyzer" : "ik_max_word",
          "search_analyzer" : "ik_smart"
        },
        "goods_type_id" : {
          "type" : "keyword"
        },
        "price_score" : {
          "type" : "long"
        },
        "sale" : {
          "type" : "integer"
        },
        "suggest" : {
          "type" : "completion",
          "analyzer" : "ik_smart",
          "preserve_separators" : true,
          "preserve_position_increments" : true,
          "max_input_length" : 50,
          "contexts" : [
            {
              "name" : "enable_cat",
              "type" : "CATEGORY",
              "path" : "enable"
            },
            {
              "name" : "delete_status_cat",
              "type" : "CATEGORY",
              "path" : "delete_status"
            }
          ]
        },
        "type" : {
          "type" : "text",
          "fields" : {
            "keyword" : {
              "type" : "keyword",
              "ignore_above" : 256
            }
          }
        }
      }
    }
  }
}

使用前缀ContextComplitationSuggester进行联想词查询

POST goods_dev/_search?pretty
{
  "_source": "suggest",
  "suggest": {
    "my-suggest":{
      "prefix":"肌肤",
      "completion":{
        "field":"suggest",
        "skip_duplicates":true,
        "size":10,
        "contexts":{
          "enable_cat":{
             "context":1
          },
          "delete_status_cat":{
            "context":0
          }
        }
      }
    }
  }
}

结果:

{
  "took" : 1,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 0,
      "relation" : "eq"
    },
    "max_score" : null,
    "hits" : [ ]
  },
  "suggest" : {
    "my-suggest" : [
      {
        "text" : "肌肤",
        "offset" : 0,
        "length" : 2,
        "options" : [
          {
            "text" : "“肌肤吸尘器123”香港CHICELAN槿念烟酰胺磨砂沐浴露温和双效去角质一瓶=沐浴露+磨砂膏",
            "_index" : "goods_dev",
            "_type" : "_doc",
            "_id" : "1323938082604585019",
            "_score" : 1.0,
            "_source" : {
              "suggest" : "“肌肤吸尘器123”香港CHICELAN槿念烟酰胺磨砂沐浴露温和双效去角质一瓶=沐浴露+磨砂膏"
            },
            "contexts" : {
              "enable_cat" : [
                "1"
              ]
            }
          },
          {
            "text" : "“肌肤吸尘器”香港CHICELAN槿念烟酰胺磨砂沐浴露温和双效去角质一瓶=沐浴露+磨砂膏",
            "_index" : "goods_dev",
            "_type" : "_doc",
            "_id" : "1323894411586834447",
            "_score" : 1.0,
            "_source" : {
              "suggest" : "“肌肤吸尘器”香港CHICELAN槿念烟酰胺磨砂沐浴露温和双效去角质一瓶=沐浴露+磨砂膏"
            },
            "contexts" : {
              "delete_status_cat" : [
                "0"
              ]
            }
          }
        ]
      }
    ]
  }
}

使用SpringDataElasticsearch进行JavaAPI查询

新建一个固定suggest查询对象,该对象构建一个查询context,类似于上面的

"contexts":{
          "enable_cat":{
             "context":1
          },
          "delete_status_cat":{
            "context":0
          }
        }
/**
     * 固定的suggest查询对象
     */
    private static  Map<String, List<? extends ToXContent>> SUGGESTION_CONTEXT = null;
    static {
        CategoryQueryContext enableCat = CategoryQueryContext.builder().setCategory("1").build();
        CategoryQueryContext deleteStatusCat = CategoryQueryContext.builder().setCategory("0").build();
        Map<String, List<? extends ToXContent>> contexts = new HashMap<>();
        List<CategoryQueryContext> list = new ArrayList<>(1);
        list.add(enableCat);
        contexts.put("enable_cat", list);
        List<CategoryQueryContext> list2 = new ArrayList<>(1);
        list2.add(deleteStatusCat);
        contexts.put("delete_status_cat",list2);
        SUGGESTION_CONTEXT = contexts;
    }

根据关键字联想查询方法

    @Override
    public List<String> associate(String keyword) {
        //使用suggest进行标题联想
        CompletionSuggestionBuilder suggest = SuggestBuilders.completionSuggestion("suggest").prefix(keyword).skipDuplicates(true).size(10).contexts(SUGGESTION_CONTEXT);
        SuggestBuilder suggestBuilder = new SuggestBuilder();
        suggestBuilder.addSuggestion("goodsNameSuggest",suggest);

        //查询
        SearchResponse goodsNameSuggestResp = elasticsearchRestTemplate.suggest(suggestBuilder, goodsIndexName);
        Suggest.Suggestion<? extends Suggest.Suggestion.Entry<? extends Suggest.Suggestion.Entry.Option>> goodsNameSuggest = goodsNameSuggestResp
                .getSuggest().getSuggestion("goodsNameSuggest");
        //处理返回
        List<String> collect = goodsNameSuggest.getEntries().stream().map(x -> x.getOptions().stream().map(y->y.getText().toString()).collect(Collectors.toList())).findFirst().get();
        return CollectionUtils.isEmpty(collect)?Collections.emptyList():collect;
    }

接口测试

接口传递keyword关键字"韩版",返回

{
  "code": 200,
  "msg": "操作成功",
  "timestamp": "1605173186443",
  "data": [
    "韩版时尚中长款衬衫 Because-t2041",
    "韩版淑女纯色休闲裤 Holicholic-sp26265",
    "韩版简约时尚连衣裙 Happy10-ds1009573",
    "韩版简约纯色衬衫 Holicholic-t26070",
    "韩版简约经典半身裙 Maybe-baby-sp30591",
    "韩版简约经典女裤套装 Holicholic-ds26260",
    "韩版纯色休闲T恤 Holicholic-t26074"
  ]
}

 

作者:老王_KICHUN
链接:https://www.jianshu.com/p/c78011dd9028
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。