背景:
在使用阿裡雲OpenSearch過程中發現官方SDK存在以下問題:
- 搜尋條件設定需要拼接字元串來實作,不太友好;
- 搜尋傳回結果是字元串類型,需要業務自己解析;
- 應用結構schema隻能hard coding;
- 有一定的學習成本,比如某種類型的索引查詢文法有哪些限制。
改進思路:
封裝一個starter,具有以下功能:
- 自動根據OpenSearch的應用結構schema生成代碼(查詢條件類和傳回結果類);
- 搜尋條件的封裝使用流式API的方式,隐藏複雜性;用API引導使用者對文法有差異的索引或屬性字段設定查詢或過濾條件。
接入步驟:
引入SDK包:
<dependency>
<groupId>com.github.zougeren</groupId>
<artifactId>fluent-search-processor</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
工程本地建立一個工具類,使用如下方式生成代碼:
package com.aliyun.gts.xaccount.user.util;
import com.aliyun.opensearch.sdk.generated.commons.OpenSearchClientException;
import com.aliyun.opensearch.sdk.generated.commons.OpenSearchException;
import com.google.common.collect.Lists;
import java.io.IOException;
/**
* @author zouge
*/
public class FluentSearchGenerator {
public static void main(String[] args) throws OpenSearchClientException, OpenSearchException, IOException, ClassNotFoundException {
com.github.zougeren.fluent.search.processor.util.FluentSearchGenerator.builder()
//配置opensearch公網接入點
.host("http://opensearch-cn-zhangjiakou.aliyuncs.com")
//填入ak和sk,注意隻是臨時填寫,生成完代碼之後一定删掉,不要送出到git裡!
.accessKey("")
.accessSecret("")
//設定要生成哪些應用的代碼,可以配置完整的應用名稱,也可以配置*通配符
//比如目前我這邊應用命名規則是庫名_表名,則可以配置"dayu_xaccount_*"來生成賬号庫涉及的所有應用代碼
.appNames(Lists.newArrayList("dayu_xaccount_*"))
//生成代碼放在哪個包
.basePkg("com.aliyun.gts.xaccount.user.search")
//生成代碼放在哪個子產品
.srcDir("xuser-platform/user-core/src/main/java")
.build()
.generate();
}
}
運作之後,每個應用會生成兩個類:
XxxSearchParam: 這個是搜尋條件的封裝;
XxxSearchResult: 這個是搜尋傳回結果的封裝;
其中Xxx表示OpenSearch控制台中的應用名稱。
應用增加配置:
fluent:
search:
#目前僅支援openSearch,後續支援es等
engine: openSearch
#配置ak和sk
openSearchAccessKey:
openSearchAccessSecret:
#openSearch接入點
openSearchHost: http://opensearch-cn-shanghai.aliyuncs.com
開始搜尋:
@Autowired
private OpenSearchClient openSearchClient;
public void test(){
//此處省略了param的詳細構造過程,可參考下方示例
ZougetestSearchParam param = new ZougetestSearchParam();
Page<ZougetestSearchResult> search = openSearchClient.search(param);
int pageNo = search.getPageNo();
int pageSize = search.getPageSize();
long total = search.getTotal();
List<ZougetestSearchResult> data = search.getData();
}
XxxSearchParam詳細文法示例:
我建了一個測試應用,應用名稱:zougetest(starter本身支援多表關聯,這裡為了測試友善我隻建了主表),包含了OpenSearch目前支援的所有類型:

所有可建立索引的字段均建立索引,some_int_field當使用關鍵字或數值分析時,query子句文法會不同,是以我建了兩個索引友善測試:
所有可設定為屬性字段的均設定為屬性字段:
示例代碼:
ZougetestSearchParam param = new ZougetestSearchParam()
//查詢所有字段
.selectAll()
//或者指定要查詢哪些字段,以end結束
.select.someIntField().someFloatField().end
//query子句,每個索引字段都有 andXxx() orXxx() andNotXxx() rankXxx() 四個方法,表示四種連接配接符
//主要分為四類:關鍵字分析、地理位置分析、數值分析、其他分析
.where
//關鍵字分析
//.rankId()
//.orSomeIntArrayField()
//.andNotSomeLiteralArrayField()
.andSomeLiteralField()
//AND some_literal_field:'hh' 每個方法都有一個含有Predicate的重載版本,後續示例不再贅述
.equal("hh", Objects::nonNull)
//AND some_literal_field:'hehe'|'haha' 目前字段繼續增加條件,則連接配接符保持和上一步的一緻
.in(Lists.newArrayList("hehe", "haha"))
//以end結束目前索引字段的條件
.end
//地理位置分析
.orSomeGeoPointField()
//OR some_geo_point_field:'point(50 51)'
.point(50d, 51d)
//OR some_geo_point_field:'circle(50 51,1000)'
.circle(50d, 51d, 1000)
//OR some_geo_point_field:'rectangle(51 52,53 54)'
.rectangle(51d, 52d, 53d, 54d)
.end
//其他分析
//.rankSomeTextField()
.rankSomeShortTextField()
//RANK some_short_text_field:'haha'
.like("haha")
//RANK some_short_text_field:'heh'|'haha'
.likes(Lists.newArrayList("heh", "haha"))
//RANK some_short_text_field:"hehe"
.phaseLike("hehe")
//RANK some_short_text_field:"heh"|"haha"
.phaseLikes(Lists.newArrayList("heh", "haha"))
.end
//數值分析
//.andSomeIntField()
.andSomeTimestampField()
//AND some_timestamp_field:[-9223372036854775808,123]
.le(123L)
//AND some_timestamp_field:[123,9223372036854775807]
.ge(123L)
//AND some_timestamp_field:[-9223372036854775808,123)
.lt(123L)
//AND some_timestamp_field:(123,9223372036854775807]
.gt(123L)
//AND some_timestamp_field:[12,23]
.rangeAllInclusive(12L, 23L)
//AND some_timestamp_field:(12,23)
.rangeAllExclusive(12L, 23L)
//AND some_timestamp_field:(12,23]
.rangeLeftExclusiveRightInclusive(12L, 23L)
//AND some_timestamp_field:[12,23)
.rangeLeftInclusiveRightExclusive(12L, 23L)
.end
//實際使用場景中,四個操作符的優先級可能大家不一定記得清楚,會使用()定義優先級,是以這裡有額外的四個方法來生成帶括号的嵌套查詢
//and() or() andNot() rank()
.and(query -> query.andSomeIntField().gt(123).end
.orId().equal(123).end)
//結束query子句,生成queryStr時.where後緊跟的第一個條件和所有括号中的第一個條件的連接配接符會被trim掉
.end
//filter子句,每個屬性字段都有 andXxx() orXxx() 兩個方法,表示兩種連接配接符
//主要分為四類:數字、數字數組、字元串、字元串數組
.filter
//數字
//.andSomeTimestampField()
//.andSomeIntField()
//.andSomeDoubleField()
.andSomeFloatField()
//AND some_float_field>123
.gt(123)
//AND some_float_field>=123
.ge(123)
//AND some_float_field<123
.lt(123)
//AND some_float_field<=123
.le(123)
//AND some_float_field=123
.equal(123)
//AND in(some_float_field,"123|345")
.in(Lists.newArrayList(123, 345))
//AND some_float_field!=123
.notEqual(123)
//AND notin(some_float_field,"123|345")
.notIn(Lists.newArrayList(123, 345))
.end
//數字數組
//.andSomeGeoPointField()
//.andSomeDoubleArrayField()
//.andSomeIntArrayField()
.orSomeFloatArrayField()
//OR some_float_array_field=123
.equal(123)
//OR some_float_array_field!=123
.notEqual(123)
//OR (some_float_array_field=123 OR some_float_array_field=345)
.in(Lists.newArrayList(123, 345))
//OR (some_float_array_field!=123 AND some_float_array_field!=345)
.notIn(Lists.newArrayList(123, 345))
.end
//字元串
.andSomeLiteralField()
//AND some_literal_field="haha"
.equal("haha")
//AND some_literal_field!="haha"
.notEqual("hehe")
//AND in(some_literal_field,"heh|jaj")
.in(Lists.newArrayList("heh", "jaj"))
//AND notin(some_literal_field,"e|a")
.notIn(Lists.newArrayList("e", "a"))
.end
//字元串數組
.andSomeLiteralArrayField()
//AND some_literal_array_field="haha"
.equal("haha")
//AND some_literal_array_field!="hah"
.notEqual("hah")
//AND (some_literal_array_field="hah" OR some_literal_array_field="heh")
.in(Lists.newArrayList("hah", "heh"))
//AND (some_literal_array_field!="a" AND some_literal_array_field!="e")
.notIn(Lists.newArrayList("a", "e"))
.end
//同樣支援嵌套查詢 and() or()
.and(filter -> filter.andSomeFloatField().le(123).end
.orSomeDoubleField().gt(123).end)
//結束filter子句,生成filterStr時.filter後緊跟的第一個條件和所有括号中的第一個條件的連接配接符會被trim掉
.end
//排序,支援自定義列名
.orderBy("some_float_field").desc().end
//或者根據具體字段排序
.orderBy.someDoubleArrayField().asc().someDoubleField().desc().end
//分頁參數
.limit(1, 10);
目前還有一些進階的查詢沒有封裝好,如果僅僅想使用結果自動轉javaBean,而查詢條件仍然使用原生的SDK,則可以使用第二個方法:
com.github.zougeren.fluent.search.processor.client.OpenSearchClient#search(com.aliyun.opensearch.sdk.generated.search.SearchParams, java.lang.Class<R>)
第二個參數傳入對應的XxxSearchResult
後續規劃:
其他進階用法(aggregate、distinct、scroll查詢、底紋、熱搜、下拉選)的封裝還在開發中,歡迎交流~