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 打包
2019-08-25_21-00-06.pngmaven clean install -Dmaven.test.skip=true
将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的版本差别真的有点大。
网友评论