美文网首页
Elasticsearch 6.5.4 自定义过滤器插件

Elasticsearch 6.5.4 自定义过滤器插件

作者: 孙瑞锴 | 来源:发表于2019-08-25 21:14 被阅读0次

    1. 借鉴

    Elasticsearch自定义过滤插件实现复杂逻辑过滤
    还有其他技术文档,比如官网,google上面的一堆,但是都是复制来复制去,计算评分的例子,唉。。

    2. 开始

    示例插件:过滤指定日期内的最小价格

    2019-08-25_20-00-49.png

    2.1 创建索引

    PUT /demo
    
    POST /demo/_mapping/demo
    {
        "properties":{
            "name":{
                "type":"text",
                "analyzer":"ik_max_word",
                "search_analyzer":"ik_smart"
            },
            "counts":{
                "type":"nested",
                "include_in_parent":true,
                "properties":{
                    "details":{
                        "type":"nested",
                        "include_in_root":true,
                        "properties":{
                            "level":{
                                "type":"keyword"
                            },
                            "price":{
                                "type":"double"
                            }
                        }
                    },
                    "date":{
                        "type":"date",
                        "format":"yyyy-MM-dd"
                    }
                }
            }
        }
    }
    

    2.2 添加测试数据

    POST /demo/demo/1
    {
      "name": "测试1",
      "counts": [
        {
          "details": [
            {
              "level": "0",
              "price": "10"
            },
            {
              "level": "1",
              "price": "20"
            },
            {
              "level": "2",
              "price": "30"
            }],
            "date": "2019-08-25"
        },
        {
          "details": [
            {
              "level": "0",
              "price": "20"
            },
            {
              "level": "1",
              "price": "30"
            },
            {
              "level": "2",
              "price": "40"
            }],
            "date": "2019-08-26"
        }]
    }
    
    POST /demo/demo/2
    {
      "name": "测试2",
      "counts": [
        {
          "details": [
            {
              "level": "0",
              "price": "20"
            },
            {
              "level": "1",
              "price": "30"
            },
            {
              "level": "2",
              "price": "40"
            }],
            "date": "2019-08-25"
        },
        {
          "details": [
            {
              "level": "0",
              "price": "50"
            },
            {
              "level": "1",
              "price": "60"
            },
            {
              "level": "2",
              "price": "70"
            }],
            "date": "2019-08-26"
        }]
    }
    

    2.3 plugin.xml

    需要注意的是plugin.xml文件必须放在assembly文件夹中,与main同级目录

    <?xml version="1.0"?>
    <assembly>
        <id>plugin</id>
        <formats>
            <format>zip</format>
        </formats>
        <includeBaseDirectory>false</includeBaseDirectory>
        <fileSets>
            <fileSet>
                <directory>${project.basedir}/src/main/resources</directory>
                <outputDirectory>/minPricePlugin</outputDirectory>
            </fileSet>
        </fileSets>
        <dependencySets>
            <dependencySet>
                <outputDirectory>/minPricePlugin</outputDirectory>
                <useProjectArtifact>true</useProjectArtifact>
                <useTransitiveFiltering>true</useTransitiveFiltering>
                <excludes>
                    <exclude>org.elasticsearch:elasticsearch</exclude>
                    <exclude>org.apache.logging.log4j:log4j-api</exclude>
                </excludes>
            </dependencySet>
        </dependencySets>
    </assembly>
    

    2.4 plugin-descriptor.properties

    #有些属性已经过时了,可以参考官网
    description=minPricePlugin
    version=1.0
    name=minPricePlugin
    #site=${elasticsearch.plugin.site}
    #jvm=true
    classname=com.ruihong.MinPricePlugin
    java.version=1.8
    elasticsearch.version=6.5.4
    #isolated=${elasticsearch.plugin.isolated}
    

    2.5 pom.xml 配置(关键部分)

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>
      </properties>
    
      <dependencies>
        <dependency>
          <groupId>junit</groupId>
          <artifactId>junit</artifactId>
          <version>4.12</version>
          <scope>test</scope>
        </dependency>
    
        <dependency>
          <groupId>org.elasticsearch.client</groupId>
          <artifactId>transport</artifactId>
          <version>6.5.4</version>
          <scope>compile</scope>
        </dependency>
      </dependencies>
    
      <build>
        <plugins>
          <plugin>
            <artifactId>maven-assembly-plugin</artifactId>
            <version>2.3</version>
            <configuration>
              <appendAssemblyId>false</appendAssemblyId>
              <outputDirectory>${project.build.directory}/releases/</outputDirectory>
              <descriptors>
                <descriptor>${basedir}/src/assembly/plugin.xml</descriptor>
              </descriptors>
            </configuration>
            <executions>
              <execution>
                <phase>package</phase>
                <goals>
                  <goal>single</goal>
                </goals>
              </execution>
            </executions>
          </plugin>
          <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-compiler-plugin</artifactId>
            <configuration>
              <source>1.8</source>
              <target>1.8</target>
            </configuration>
          </plugin>
        </plugins>
      </build>
    

    2.6 com.ruihong.MinPricePlugin

    package com.ruihong;
    
    import com.ruihong.ex02.MinPriceScriptEngine;
    import org.elasticsearch.common.settings.Settings;
    import org.elasticsearch.plugins.Plugin;
    import org.elasticsearch.plugins.ScriptPlugin;
    import org.elasticsearch.script.ScriptContext;
    import org.elasticsearch.script.ScriptEngine;
    
    import java.util.Arrays;
    import java.util.Collection;
    
    public class MinPricePlugin extends Plugin implements ScriptPlugin
    {
        @Override
        public ScriptEngine getScriptEngine(Settings settings, Collection<ScriptContext<?>> contexts)
        {
            System.out.println(String.format("contexts: %s", Arrays.toString(contexts.toArray())));
    //        log.info("contexts : {} ", Arrays.toString(contexts.toArray()));
            return new MinPriceScriptEngine();
        }
    }
    

    2.7 com.ruihong.ex02.MinPriceScriptEngine

    package com.ruihong.ex02;
    
    import org.apache.http.impl.nio.reactor.ExceptionEvent;
    import org.apache.lucene.index.LeafReaderContext;
    import org.elasticsearch.script.FilterScript;
    import org.elasticsearch.script.ScriptContext;
    import org.elasticsearch.script.ScriptEngine;
    import org.elasticsearch.script.SearchScript;
    
    import java.io.IOException;
    import java.text.ParseException;
    import java.text.SimpleDateFormat;
    import java.util.Comparator;
    import java.util.Date;
    import java.util.List;
    import java.util.Map;
    
    public class MinPriceScriptEngine implements ScriptEngine
    {
        //每一个线程
        private static final ThreadLocal<SimpleDateFormat> threadLocal = new
                ThreadLocal<SimpleDateFormat>();
    
        @Override
        public String getType()
        {
            return "min-price";
        }
    
        @Override
        public <FactoryType> FactoryType compile(String name, String code, ScriptContext<FactoryType> context, Map<String, String> params)
        {
            System.out.println(String.format("MinPriceScriptEngine use params the scriptName %s, scriptSource %s, context %s, params %s", name, code, context.name, params.entrySet()));
    
            if (!context.equals(FilterScript.CONTEXT))
            {
                throw new IllegalArgumentException(getType() + " scripts cannot be used for context [" + context.name + "]");
            }
    
            if ("min-price-filter".equals(code))
            {
                FilterScript.Factory factory = (p, lookupl) -> (FilterScript.LeafFactory) ctx ->
                        new FilterScript(p, lookupl, ctx) {
                            @Override
                            public boolean execute()
                            {
                                try
                                {
                                    final Double min = Double.valueOf(p.get("price-gte").toString());
                                    final Double max = Double.valueOf(p.get("price-lt").toString());
                                    final Date start = covertDateStrToDate(p.get("time-gte").toString(), "yyyy-MM-dd");
                                    final Date end = covertDateStrToDate(p.get("time-lt").toString(), "yyyy-MM-dd");
                                    final String level = p.get("level").toString();
    
                                    Double minPrice = ((List<Map<String, Object>>) lookupl.source().get("counts"))
                                            .stream().filter(item ->
                                            {
                                                Date sellDate = covertDateStrToDate(item.get("date").toString(), "yyyy-MM-dd");
                                                return sellDate.compareTo(start) >= 0 && sellDate.compareTo(end) < 0);
                                            })
                                            .flatMap(item -> ((List<Map<String, Object>>) item.get("details")).stream())
                                            .filter(item -> item.get("level").toString().equals(level))
                                            .map(item -> Double.valueOf(item.get("price").toString()))
                                            .min(Comparator.naturalOrder())
                                            .orElse(-1.0);
                                    if (minPrice >= min && minPrice < max)
                                    {
                                        return true;
                                    }
                                }catch (Exception ex)
                                {
                                    ex.printStackTrace();
                                }
                                return false;
                            }
                        };
                return context.factoryClazz.cast(factory);
            }
            throw new IllegalArgumentException("Unknown script name " + code);
        }
    
        public static Date covertDateStrToDate(String dateStr, String format)
        {
            SimpleDateFormat sdf = null;
            sdf = threadLocal.get();
            if (sdf == null)
            {
                sdf = new SimpleDateFormat(format);
            }
    
            Date date = null;
            try
            {
                date = sdf.parse(dateStr);
            } catch (ParseException e)
            {
                e.printStackTrace();
            }
            return date;
        }
    }
    

    2.8 打包

    maven clean install -Dmaven.test.skip=true

    2019-08-25_21-00-06.png

    将releases目录下的zip放到es的plugins目录下,解压zip并重启es

    2.9 测试

    使用kibana

    GET /demo/_search
    {
      "query": {
        "bool": {
          "filter": {
            "script": {
              "script": {
                "source": "min-price-filter",
                "lang": "min-price",
                "params": {
                  "price-gte": "0",
                  "price-lt": "100",
                  "level": "0",
                  "time-gte": "2019-08-25",
                  "time-lt": "2019-08-26"
                }
              }
            }
          }
        }
      }
    }
    

    或者是使用rest api

    public interface ElasticsearchPluginConstants
    {
        // 最低价插件
        interface MinPrice
        {
            String SOURCE = "min-price-filter";
            String LANG = "min-price";
    
            interface Param
            {
                String PARAM_PRICE_GTE = "price-gte";
                String PARAM_PRICE_LT = "price-lt";
                String PARAM_LEVEL = "level";
                String PARAM_TIME_GTE = "time-gte";
                String PARAM_TIME_LT = "time-lt";
            }
        }
    }
    
    Map<String, Object> params = new HashMap<>();        
    params.put(ElasticsearchPluginConstants.MinPrice.Param.PARAM_LEVEL, "0");
    params.put(ElasticsearchPluginConstants.MinPrice.Param.PARAM_PRICE_GTE,  0);
    params.put(ElasticsearchPluginConstants.MinPrice.Param.PARAM_PRICE_LT,  100);
    params.put(ElasticsearchPluginConstants.MinPrice.Param.PARAM_TIME_GTE, "2019-08-25");
    params.put(ElasticsearchPluginConstants.MinPrice.Param.PARAM_TIME_LT, "2019-08-26");
    boolQueryBuilder.filter(QueryBuilders.scriptQuery(new Script(ScriptType.INLINE, ElasticsearchPluginConstants.MinPrice.LANG, ElasticsearchPluginConstants.MinPrice.SOURCE, params)));
    

    3. 大功告成

    这里进行判断context是否是:FilterScript.CONTEXT,由此可见还有其他类型,这个用idea看下包结构,看下继承关系就能定义其他插件了。另外,es的版本差别真的有点大。

    相关文章

      网友评论

          本文标题:Elasticsearch 6.5.4 自定义过滤器插件

          本文链接:https://www.haomeiwen.com/subject/geykectx.html