應用場景
"()" "&" "|"? 這幾個條件對于我們來說并不陌生, 其表達的邏輯非常明了, 又能通過很少的字符表達很復雜的嵌套關系, 在一些復雜的查詢中會經常用到, 因此我最近也遇到了類似的問題,一開始覺得這類的工具應該挺常見的, 結果搜了半天沒有找到合適的,因此決定自己寫一個
簡介
此工具的復雜之處在于我們并不確定操作系統的人員會輸入怎樣的表達式,格式并不是固定的因此可能會書寫出較為復雜的邏輯. 也有可能只嵌套一層就結束了,所以我們的代碼一定要考慮的通用
此處我簡單說一下它的原理, 主要是用到了一個java中棧的概念: 這個工具通過解析輸入的邏輯查詢字符串,使用棧來管理運算符和操作數,構建出對應的查詢樹,然后將其轉換為Elasticsearch的多字段(如標題、摘要、正文)的搜索查詢,實現復雜的邏輯查詢條件的自動解析和執行。
以下代碼全部都加了注釋, 應該是不難理解的?
代碼
package com.sinosoft.springbootplus.lft.business.dispatch.publicopinion.util;import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.builder.SearchSourceBuilder;import java.util.Stack;/*** 構建ES復雜查詢條件,包含括號、邏輯運算符和操作符** @author zzt* @date 2024-05-28*/
public class ESQueryParserUtil {/*** 解析輸入字符串并將其轉換為Elasticsearch的QueryBuilder** @param query 輸入的查詢字符串* @return Elasticsearch的QueryBuilder*/public static SearchSourceBuilder parseQuery(String query) {// 存儲運算符的棧Stack<Character> operators = new Stack<>();// 存儲操作數的棧Stack<QueryBuilder> operands = new Stack<>();for (int i = 0; i < query.length(); i++) {char ch = query.charAt(i);if (ch == '(' || ch == '&' || ch == '|') {// 遇到左括號或者運算符時,壓入運算符棧operators.push(ch);} else if (ch == ')') {// 遇到右括號時,彈出運算符棧中的運算符并進行計算直到遇到左括號while (!operators.isEmpty() && operators.peek() != '(') {char operator = operators.pop();QueryBuilder right = operands.pop();QueryBuilder left = operands.pop();operands.push(applyOperator(left, right, operator));}operators.pop(); // 彈出左括號} else if (Character.isLetterOrDigit(ch) || ch == ' ') {// 遇到字母、數字、空格或者“地區”時,構建查詢字符串StringBuilder sb = new StringBuilder();while (i < query.length() && (Character.isLetterOrDigit(query.charAt(i)) || query.charAt(i) == ' ')) {sb.append(query.charAt(i));i++;}i--; // 回退一個字符,因為外層for循環會前進一個字符operands.push(QueryBuilders.multiMatchQuery(sb.toString().trim(), "title", "sysAbstract", "content"));//此處是我的ES中要模糊搜索的三個字段, 這里請自行更改}}// 處理剩余的運算符while (!operators.isEmpty()) {char operator = operators.pop();QueryBuilder right = operands.pop();QueryBuilder left = operands.pop();operands.push(applyOperator(left, right, operator));}return new SearchSourceBuilder().query(operands.pop());}/*** 根據運算符將兩個操作數組合成一個QueryBuilder** @param left 左操作數* @param right 右操作數* @param operator 運算符* @return 組合后的QueryBuilder*/private static QueryBuilder applyOperator(QueryBuilder left, QueryBuilder right, char operator) {BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();if (operator == '&') {boolQuery.must(left).must(right);} else if (operator == '|') {boolQuery.should(left).should(right);}return boolQuery;}public static void main(String[] args) {String query = "((北京|天津|(河北&石家莊))&(打架|辱罵|違法))&(中國)";SearchSourceBuilder searchSourceBuilder = parseQuery(query);System.out.println(searchSourceBuilder);}
}
?生成的查詢條件
由于我寫的這個算是稍微復雜一點的嵌套,生成的查詢條件還是挺長的, 感興趣的可以試一下
{"query": {"bool": {"must": [{"bool": {"must": [{"bool": {"should": [{"multi_match": {"query": "北京","fields": ["content^1.0","sysAbstract^1.0","title^1.0"],"type": "best_fields","operator": "OR","slop": 0,"prefix_length": 0,"max_expansions": 50,"zero_terms_query": "NONE","auto_generate_synonyms_phrase_query": true,"fuzzy_transpositions": true,"boost": 1.0}},{"bool": {"should": [{"multi_match": {"query": "天津","fields": ["content^1.0","sysAbstract^1.0","title^1.0"],"type": "best_fields","operator": "OR","slop": 0,"prefix_length": 0,"max_expansions": 50,"zero_terms_query": "NONE","auto_generate_synonyms_phrase_query": true,"fuzzy_transpositions": true,"boost": 1.0}},{"bool": {"must": [{"multi_match": {"query": "河北","fields": ["content^1.0","sysAbstract^1.0","title^1.0"],"type": "best_fields","operator": "OR","slop": 0,"prefix_length": 0,"max_expansions": 50,"zero_terms_query": "NONE","auto_generate_synonyms_phrase_query": true,"fuzzy_transpositions": true,"boost": 1.0}},{"multi_match": {"query": "石家莊","fields": ["content^1.0","sysAbstract^1.0","title^1.0"],"type": "best_fields","operator": "OR","slop": 0,"prefix_length": 0,"max_expansions": 50,"zero_terms_query": "NONE","auto_generate_synonyms_phrase_query": true,"fuzzy_transpositions": true,"boost": 1.0}}],"adjust_pure_negative": true,"boost": 1.0}}],"adjust_pure_negative": true,"boost": 1.0}}],"adjust_pure_negative": true,"boost": 1.0}},{"bool": {"should": [{"multi_match": {"query": "打架","fields": ["content^1.0","sysAbstract^1.0","title^1.0"],"type": "best_fields","operator": "OR","slop": 0,"prefix_length": 0,"max_expansions": 50,"zero_terms_query": "NONE","auto_generate_synonyms_phrase_query": true,"fuzzy_transpositions": true,"boost": 1.0}},{"bool": {"should": [{"multi_match": {"query": "辱罵","fields": ["content^1.0","sysAbstract^1.0","title^1.0"],"type": "best_fields","operator": "OR","slop": 0,"prefix_length": 0,"max_expansions": 50,"zero_terms_query": "NONE","auto_generate_synonyms_phrase_query": true,"fuzzy_transpositions": true,"boost": 1.0}},{"multi_match": {"query": "違法","fields": ["content^1.0","sysAbstract^1.0","title^1.0"],"type": "best_fields","operator": "OR","slop": 0,"prefix_length": 0,"max_expansions": 50,"zero_terms_query": "NONE","auto_generate_synonyms_phrase_query": true,"fuzzy_transpositions": true,"boost": 1.0}}],"adjust_pure_negative": true,"boost": 1.0}}],"adjust_pure_negative": true,"boost": 1.0}}],"adjust_pure_negative": true,"boost": 1.0}},{"multi_match": {"query": "中國","fields": ["content^1.0","sysAbstract^1.0","title^1.0"],"type": "best_fields","operator": "OR","slop": 0,"prefix_length": 0,"max_expansions": 50,"zero_terms_query": "NONE","auto_generate_synonyms_phrase_query": true,"fuzzy_transpositions": true,"boost": 1.0}}],"adjust_pure_negative": true,"boost": 1.0}}
}
?