因业务需要,需要使用Elasticsearch对不同的索引进行综合查询,这些索引没有任何联系,如:书籍和用户索引。本文讨论如何解决此类问题。
搜索总有一个唯一关键词,即使是不同的索引,也是如此。所以,针对不同的索引,可以使用multi_match进行处理。
书籍索引有价格:price,用户索引有性别。但书籍没有性别,用户没有价格。如果做搜索时,不仅需要按照关键字搜索,针对书籍和用户需要分别做价格和性别处理,使得价格不影响用户,性别不影响书籍,又该怎么处理呢?可以使用filter结合scrpit进行处理。
综上所述,查询脚本如下:
GET /user,book/_search
{
"from": 0,
"size": 10,
"query": {
"bool": {
"must": [
{
"multi_match": {
"query": "张三",
"fields": ["mc","name","bz","shuom","auth"]
}
}
],
"filter": {
"script": {
"script": {
"source": """
if(doc._index.value == 'book'){
return true
}
if(doc._index.value == 'user'){
return doc['sex'].value.startsWith('男') && doc['age'].value == 25
}
return false
""",
"lang": "painless"
}
}
}
}
}
}
1、自定义注解使得索引库与JavaBean映射
package com.jx.aisino.anno.es;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface EsIndex {
public String index();
}
2、自定义multi_match注解,针对关键词处理
package com.jx.aisino.anno.es;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface EsMustMultMatch {
public String name();
}
3、自定义script注解对不同索引条件处理
package com.jx.aisino.anno.es;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface EsScript {
public String paramName();
}
4、注解解析器
package com.jx.aisino.anno.es;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import com.jx.aisino.constant.Constant;
import com.jx.aisino.util.ClassTools;
public class EsParser {
public static Map parse(String packageName) throws Exception{
Map> index_class_map = new HashMap>();
Set mateh = new HashSet();
Map queryParams = new HashMap();
List list = ClassTools.scanClassName(packageName);
Class> clazz = null;
for (String className : list) {
clazz = Class.forName(className);
List fields = ClassTools.getAllField(clazz);
if(fields != null && fields.size() > 0){
EsIndex in = null;
EsMustMultMatch ma = null;
EsUrlParams urlParams = null;
in = clazz.getDeclaredAnnotation(EsIndex.class);
if(in != null){
index_class_map.put(in.index(), clazz);
for (Field field : fields) {
field.setAccessible(true);
ma = field.getDeclaredAnnotation(EsMustMultMatch.class);
if(ma != null){
mateh.add(ma.name());
}
urlParams = field.getDeclaredAnnotation(EsUrlParams.class);
if(urlParams != null){
queryParams.put(field.getName(),urlParams.key());
}
}
}
}
}
Map result = new HashMap();
result.put(Constant.ES_KEYWORD_FIELDS, mateh);
result.put(Constant.ES_INDEX_CLASS_MAP, index_class_map);
result.put(Constant.ES_URL_PARAMS_MAP, queryParams);
return result;
}
}
5、接口层
package com.jx.aisino.controller.es;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
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.BoolQueryBuilder;
import org.elasticsearch.index.query.MultiMatchQueryBuilder;
import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.index.query.ScriptQueryBuilder;
import org.elasticsearch.script.Script;
import org.elasticsearch.script.ScriptType;
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.HighlightBuilder.Field;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightField;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.jx.aisino.anno.es.EsHighLightName;
import com.jx.aisino.anno.es.EsUrl;
import com.jx.aisino.anno.es.EsUrlParams;
import com.jx.aisino.constant.Constant;
import com.jx.aisino.init.AisinoContext;
import com.jx.aisino.interceptor.threadlocal.LoginThreadLocal;
import com.jx.aisino.model.es.EsSearchResult;
import com.jx.aisino.pojo.Result;
import com.jx.aisino.util.StringUtil;
@RestController
@CrossOrigin
@RequestMapping(value = "es")
public class EsController {
private static final Logger LOGGER = LoggerFactory.getLogger(EsController.class);
@Autowired
private AisinoContext context;
@Autowired
private RestHighLevelClient esClient;
@RequestMapping(value = "/search",method = RequestMethod.POST)
public ResponseEntity search(String keyWord, String sfzh,@RequestParam(defaultValue="1")Integer page, @RequestParam(defaultValue="20")Integer rows){
Result result = null;
try {
Map map = new HashMap();
ObjectMapper mapper = new ObjectMapper();
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
//索引库,项目启动时,自动扫描包下带有EsIndex注解而来,此处手动指定了
String[] indexs = new String[]{"jfpc","zdgkryxx"};
Map> index_script_map = new HashMap>(indexs.length);
//添加不同索引库各自的条件
List jfpc = new ArrayList();
List zdgkryxx = new ArrayList();
index_script_map.put("jfpc", jfpc);
index_script_map.put("zdgkryxx", zdgkryxx);
String khh = LoginThreadLocal.get().getKhh();
jfpc.add("doc['khh'].value == '" + khh + "'");
if(StringUtil.isNotBlank(sfzh)){
jfpc.add("doc['sfzh'].value == '" + sfzh + "'");
zdgkryxx.add("doc['sfzh'].value == '" + sfzh + "'");
}
//搜索主题
SearchSourceBuilder builder = new SearchSourceBuilder();
builder.from(rows * (page -1));
builder.size(rows);
//bool查询
BoolQueryBuilder boolQueryBuilder = new BoolQueryBuilder();
//must体
List must = boolQueryBuilder.must();
//添加关键字条件,项目启动时自动扫描类上有EsIndex,且用EsMustMultMatch注解标注的字段
String[] esFields = this.context.getEsFields();
must.add(new MultiMatchQueryBuilder(keyWord, esFields));
String code = this.parseScrpit(indexs,index_script_map);
if(code != null && !"".equals(code)){
List filter = boolQueryBuilder.filter();
Script script = new Script(ScriptType.INLINE, Script.DEFAULT_SCRIPT_LANG, code, new HashMap());
filter.add(new ScriptQueryBuilder(script));
}
builder.query(boolQueryBuilder);
//设置高亮
List fields = new ArrayList(esFields.length);
for (String esField : esFields) {
fields.add(new Field(esField));
}
HighlightBuilder highlighterBulider = new HighlightBuilder();
highlighterBulider.preTags("<"+Constant.ES_HIGHTLIGHT+">");
highlighterBulider.postTags(""+Constant.ES_HIGHTLIGHT_END+">");
highlighterBulider.fields().addAll(fields);
builder.highlighter(highlighterBulider);
SearchRequest request = new SearchRequest(indexs);
request.source(builder);
SearchResponse response = this.esClient.search(request, RequestOptions.DEFAULT);
SearchHits hits = response.getHits();
long total = hits.getTotalHits().value;
map.put("total", total);
List