天天看點

OpenSearch流式SDK分享背景:改進思路:接入步驟:後續規劃:

背景:

在使用阿裡雲OpenSearch過程中發現官方SDK存在以下問題:

  1. 搜尋條件設定需要拼接字元串來實作,不太友好;
  2. 搜尋傳回結果是字元串類型,需要業務自己解析;
  3. 應用結構schema隻能hard coding;
  4. 有一定的學習成本,比如某種類型的索引查詢文法有哪些限制。

改進思路:

封裝一個starter,具有以下功能:

  1. 自動根據OpenSearch的應用結構schema生成代碼(查詢條件類和傳回結果類);
  2. 搜尋條件的封裝使用流式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目前支援的所有類型:

OpenSearch流式SDK分享背景:改進思路:接入步驟:後續規劃:

所有可建立索引的字段均建立索引,some_int_field當使用關鍵字或數值分析時,query子句文法會不同,是以我建了兩個索引友善測試:

OpenSearch流式SDK分享背景:改進思路:接入步驟:後續規劃:

所有可設定為屬性字段的均設定為屬性字段:

OpenSearch流式SDK分享背景:改進思路:接入步驟:後續規劃:

示例代碼:

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查詢、底紋、熱搜、下拉選)的封裝還在開發中,歡迎交流~