Elasticsearch综合查询

背景

因业务需要,需要使用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"
        }
      }
    }
   }
  }
}

Java实现

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("");
			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 list = new ArrayList();
	    	SearchHit[] hitsBean = hits.getHits();
	    	if(total > 0){
	    		String index = null;
	    		Object bean = null;
	    		for (SearchHit theHit : hitsBean) {
	    			index = theHit.getIndex();
	    			bean = mapper.readValue(theHit.getSourceAsString(), this.context.getClassByEsIndex(index));
	    			this.setHighLight(bean,theHit.getHighlightFields());
	    			list.add(this.bean2EsSearchBean(bean));
				}
	    	}
	    	map.put("list", list);
			result = new Result(Result.SUCCESS, Result.SUCCESS_MSG, map);
			return ResponseEntity.ok(result);
		} catch (Exception e) {
			e.printStackTrace();
			LOGGER.error(e.getMessage());
		}
		result = new Result(Result.ERROR, Result.ERROR_MSG, "搜索失败!");
		return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(result);
	}
	
	/**
	 * 业务对象转前端可解析对象
	 * @param bean
	 * @return
	 * @throws IllegalAccessException 
	 * @throws IllegalArgumentException 
	 */
	private EsSearchResult bean2EsSearchBean(Object bean) throws Exception {
		//获取url
		Class clazz = bean.getClass();
		String url = clazz.getDeclaredAnnotation(EsUrl.class).url();
		java.lang.reflect.Field[] fields = clazz.getDeclaredFields();
		//设置请求参数
		EsUrlParams esUrlParams = null;
		EsHighLightName esHighLightName = null;
		String value = null;
		Map titleOrtextMap = new HashMap();
		for (java.lang.reflect.Field field : fields) {
			field.setAccessible(true);
			esUrlParams = field.getDeclaredAnnotation(EsUrlParams.class);
			if(esUrlParams != null){
				url += "&"+this.context.getEsUrlParams(field.getName())+"="+field.get(bean);
			}
			//获取高亮显示字段
			esHighLightName = field.getDeclaredAnnotation(EsHighLightName.class);
			if(esHighLightName != null){
				value = field.get(bean).toString();
				if(value != null && value.contains(Constant.ES_HIGHTLIGHT_END)){
					titleOrtextMap.put(esHighLightName.name(), field.get(bean).toString());
				}
			}
		}
		//设置内容和标题
		Map result = new LinkedHashMap();
		titleOrtextMap.entrySet().stream().sorted(Map.Entry.comparingByValue(new Comparator() {

			@Override
			public int compare(String o1, String o2) {
				return o1.length() - o2.length();
			}
		})).forEachOrdered(x -> result.put(x.getKey(), x.getValue()));
		EsSearchResult uiBean = new EsSearchResult();
		java.lang.reflect.Field laiyField = clazz.getDeclaredField("laiy");
		String laiy = "未知";
		if(laiyField != null){
			laiyField.setAccessible(true);
			laiy = laiyField.get(bean).toString();
		}
		uiBean.setLaiy(laiy);
		uiBean.setUrl(url);
		int count = 1;
		String text = "";
		for (Entry entry : result.entrySet()) {
			if(count == 1){
				uiBean.setTitle("
" + entry.getValue()+"
"); count++; if(result.size() > 1){ continue; } } text += "
"+entry.getKey() + ":" + entry.getValue()+"
"; } uiBean.setText(text); return uiBean; } /** * 字段高亮 * @param bean * @param highlightFields * @throws SecurityException * @throws NoSuchFieldException */ private void setHighLight(Object bean,Map highlightFields) throws Exception { if(highlightFields != null && highlightFields.size() > 0){ Class clazz = bean.getClass(); Text[] texts = null; String content = null; java.lang.reflect.Field field = null; for (Entry entry : highlightFields.entrySet()) { texts = entry.getValue().fragments(); content = ""; for (Text text : texts) { content += text.toString(); } field = clazz.getDeclaredField(entry.getKey()); field.setAccessible(true); String[] _text = content.split("<"+Constant.ES_HIGHTLIGHT+">"); content = ""; for (String value : _text) { content += value; } field.set(bean, content); } } } private String parseScrpit(String[] indexs,Map> index_script_map) { if(indexs != null && indexs.length > 0){ List list = null; String script = null; StringBuffer sb = new StringBuffer(); for (String index : indexs) { script = null; list = index_script_map.get(index); if(list != null && list.size() > 0){ script = "if(doc._index.value == '"+index+"'){return"; for (int i = 0; i< list.size(); i++) { if(i == 0){ script += " " + list.get(i); }else{ script += " " + "&&" + " " + list.get(i); } } sb.append(script + "}"); }else{ sb.append("if(doc._index.value == '"+index+"'){return true}"); } } sb.append("return false"); return sb.toString(); } return null; } }

你可能感兴趣的:(java)