欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

在golang中使用cel的用法詳解

 更新時(shí)間:2023年11月07日 08:46:15   作者:demo007x  
CEL?是一種非圖靈完備的表達(dá)式語言?,旨在快速、可移植且執(zhí)行安全,CEL?可以單獨(dú)使用,也可以嵌入到其他的產(chǎn)品中,本文將給大家介紹一下golang中如何使用cel,需要的朋友可以參考下

什么是CEL?

CEL 是一種非圖靈完備的表達(dá)式語言 ,旨在快速、可移植且執(zhí)行安全。CEL 可以單獨(dú)使用,也可以嵌入到其他的產(chǎn)品中。

CEL被設(shè)計(jì)成一種可以安全地執(zhí)行用戶代碼的語言。雖然盲目調(diào)用用戶的python代碼是危險(xiǎn)的,但您可以安全地執(zhí)行用戶的CEL代碼。由于CEL防止了會(huì)降低其性能的行為,因此它的評估安全性在納秒到微秒之間;它非常適合性能關(guān)鍵型應(yīng)用程序。eval()

CEL 計(jì)算表達(dá)式,類似于單行函數(shù)或 lambda 表達(dá)式。雖然 CEL 通常用于布爾決策,但它也可用于構(gòu)造更復(fù)雜的對象,如 JSONprotobuf 消息。

關(guān)鍵概念

應(yīng)用

CEL是通用的,已用于各種應(yīng)用程序,從路由RPC到定義安全策略。CEL是可擴(kuò)展的,與應(yīng)用程序無關(guān),并針對一次編譯、多次評估的工作流進(jìn)行了優(yōu)化。 許多服務(wù)和應(yīng)用程序評估聲明性配置。例如,基于角色的訪問控制(RBAC)是一種聲明性配置,它在給定角色和一組用戶的情況下生成訪問決策。如果聲明性配置是80%的用例,那么當(dāng)用戶需要更強(qiáng)的表達(dá)能力時(shí),CEL是一個(gè)有用的工具,可以將剩余的20%取整。

編譯

表達(dá)式是針對環(huán)境編譯的。編譯步驟生成protobuf 形式的抽象語法樹(AST)。編譯后的表達(dá)式通常會(huì)存儲(chǔ)起來以備將來使用,從而使求值盡可能快。單個(gè)編譯表達(dá)式可以使用許多不同的輸入進(jìn)行求值。

抽象語法樹:

在計(jì)算機(jī)科學(xué)中,抽象語法樹Abstract Syntax Tree,AST),或簡稱語法樹(Syntax tree),是源代碼語法結(jié)構(gòu)的一種抽象表示。它以樹狀的形式表現(xiàn)編程語言的語法結(jié)構(gòu),樹上的每個(gè)節(jié)點(diǎn)都表示源代碼中的一種結(jié)構(gòu)。之所以說語法是“抽象”的,是因?yàn)檫@里的語法并不會(huì)表示出真實(shí)語法中出現(xiàn)的每個(gè)細(xì)節(jié)。比如,嵌套括號被隱含在樹的結(jié)構(gòu)中,并沒有以節(jié)點(diǎn)的形式呈現(xiàn);而類似于 if-condition-then 這樣的條件跳轉(zhuǎn)語句,可以使用帶有三個(gè)分支的節(jié)點(diǎn)來表示。

和抽象語法樹相對的是具體語法樹(通常稱作分析樹)。一般的,在源代碼的翻譯和編譯過程中,語法分析器創(chuàng)建出分析樹,然后從分析樹生成AST。一旦AST被創(chuàng)建出來,在后續(xù)的處理過程中,比如語義分析階段,會(huì)添加一些信息。

表達(dá)式

用戶定義表達(dá)式;服務(wù)和應(yīng)用程序定義了它運(yùn)行的環(huán)境。函數(shù)簽名聲明輸入,并在CEL表達(dá)式之外編寫。CEL 可用的函數(shù)庫是自動(dòng)導(dǎo)入的。 在下面的示例中,表達(dá)式采用一個(gè)請求對象,該請求包括一個(gè)聲明令牌。該表達(dá)式返回一個(gè)布爾值,指示聲明令牌是否仍然有效。

// 通過檢查“exp”聲明來檢查JSON Web令牌是否已過期。
//
// Args:
//   claims - authentication claims.
//   now    - timestamp indicating the current system time.
// 如果令牌已過期,則返回:true
//
timestamp(claims["exp"]) < now

環(huán)境

環(huán)境是由服務(wù)定義的。嵌入CEL的服務(wù)和應(yīng)用程序聲明表達(dá)式環(huán)境。環(huán)境是可以在表達(dá)式中使用的變量和函數(shù)的集合。 CEL 類型檢查器使用基于原型的聲明來確保表達(dá)式中的所有標(biāo)識符和函數(shù)引用都得到了正確的聲明和使用。

解析表達(dá)式的三個(gè)階段

處理表達(dá)式有三個(gè)階段:解析檢查求值CEL最常見的模式是控制平面在配置時(shí)解析和檢查表達(dá)式,并存儲(chǔ)AST

在運(yùn)行時(shí),數(shù)據(jù)平面會(huì)重復(fù)檢索和評估AST。CEL 針對運(yùn)行時(shí)效率進(jìn)行了優(yōu)化,但不應(yīng)在延遲關(guān)鍵的代碼路徑中進(jìn)行解析和檢查。

CEL使用ANTLR lexer/parser語法從人類可讀的表達(dá)式解析為抽象語法樹。解析階段發(fā)出一個(gè)基于原型的抽象語法樹,其中AST中的每個(gè)Expr節(jié)點(diǎn)都包含一個(gè)整數(shù)id,用于索引解析和檢查期間生成的元數(shù)據(jù)。解析過程中生成的syntax.proto忠實(shí)地表示了以字符串形式鍵入的內(nèi)容的抽象表示。

一旦解析了表達(dá)式,就可以根據(jù)環(huán)境對其進(jìn)行檢查,以確保表達(dá)式中的所有變量和函數(shù)標(biāo)識符都已聲明并正確使用。類型檢查器生成一個(gè)checked.proto,其中包括類型、變量和函數(shù)解析元數(shù)據(jù),可以顯著提高評估效率。

評估CEL需要3件事:

  • 任何自定義擴(kuò)展的函數(shù)綁定

  • 變量綁定

  • AST評估

函數(shù)和變量綁定應(yīng)該與用于編譯AST的綁定相匹配。這些輸入中的任何一個(gè)都可以在多個(gè)評估中重復(fù)使用,例如在多組變量綁定中評估AST,或者在多個(gè)AST中使用相同的變量,或者在進(jìn)程的整個(gè)生命周期中使用函數(shù)綁定(常見情況)。

CEL 適合您的項(xiàng)目嗎?

由于 CEL 以納秒到微秒為單位評估 AST 的表達(dá)式,因此 CEL 的理想用例是具有性能關(guān)鍵路徑的應(yīng)用程序。不應(yīng)在關(guān)鍵路徑中將 CEL 代碼編譯到 AST 中;理想的應(yīng)用程序是經(jīng)常執(zhí)行配置且修改頻率相對較低的應(yīng)用程序。

例如,對服務(wù)的每個(gè) HTTP 請求執(zhí)行安全策略是 CEL 的理想用例,因?yàn)榘踩呗院苌俑模⑶?CEL 對響應(yīng)時(shí)間的影響可以忽略不計(jì)。在這種情況下,CEL 將返回一個(gè)布爾值(無論是否允許該請求),但它可能會(huì)返回更復(fù)雜的消息。

在 golang 中如何使用 CEL

一下代碼我們使用 golang 的 cel 包 github.com/google/cel-go/cel

使用 cel 進(jìn)行字符串拼接:

字符串 str = "Hello world! I'm " + name + "."中存在變量 name ,在我們的程序中,這個(gè) name 是一個(gè)變量,需要在程序中替換為具體的值,比如:張三

步驟如下:

1、先初始化 env,也就是我們上面說的需要配置執(zhí)行的環(huán)境

2、在環(huán)境中綁定變量 name 以及類型

3、env.Compile(str) 就是做了我們上面所說的編譯并解析表達(dá)式,返回 str 所對應(yīng)的ast

4、程序求值。我們將 name 需要的具體值傳到 program 中執(zhí)行 values := map[string]interface{}{"name": "CEL"}

5、獲取到最后的結(jié)果

func Test_exprReplacement(t *testing.T) {
    var str = `"Hello world! I'm " + name + "."`
    env, err := cel.NewEnv(
        cel.Variable("name", cel.StringType), // 參數(shù)類型綁定
    )
    if err != nil {
        t.Fatal(err)
    }
    ast, iss := env.Compile(str) // 編譯,校驗(yàn),執(zhí)行 str
    if iss.Err() != nil {
        t.Fatal(iss.Err())
    }
    program, err := env.Program(ast)
    if err != nil {
        t.Fatal(err)
    }
  // 初始化 name 變量的值
    values := map[string]interface{}{"name": "CEL"}
  // 傳給內(nèi)部程序并返回執(zhí)行的結(jié)果
    out, detail, err := program.Eval(values)
    if err != nil {
        t.Fatal(err)
    }
    fmt.Println(detail)
    fmt.Println(out)
}

測試結(jié)果:

Running tool: /usr/local/go/bin/go test -timeout 10s -run ^Test_exprReplacement$ github.com/demo007x/goexpr -v

=== RUN   Test_exprReplacement
Hello world! I'm CEL.
<nil>
--- PASS: Test_exprReplacement (0.00s)
PASS
ok      github.com/demo007x/goexpr    0.010s

計(jì)算一個(gè)表達(dá)式的邏輯結(jié)果:

返回表達(dá)式 var str = 100 + 200 >= 300 的執(zhí)行結(jié)果:

執(zhí)行步驟跟上面的一樣,這里就省略了。

func Test_LogicExpr1(t *testing.T) {
    var str = `100 + 200 >= 300`
    env, err := cel.NewEnv()
    if err != nil {
        t.Fatal(err)
    }
    ast, iss := env.Compile(str)
    if iss.Err() != nil {
        t.Fatal(iss.Err())
    }
    prog, err := env.Program(ast)
    if err != nil {
        t.Fatal(err)
    }
    out, detail, err := prog.Eval(map[string]interface{}{})
    if err != nil {
        t.Fatal(err)
    }
    fmt.Println(out)
    fmt.Println(detail)
}

輸出結(jié)果:

Running tool: /usr/local/go/bin/go test -timeout 10s -run ^Test_LogicExpr1$ github.com/demo007x/goexpr -v

=== RUN   Test_LogicExpr1
true
<nil>
--- PASS: Test_LogicExpr1 (0.00s)
PASS
ok      github.com/demo007x/goexpr    0.010s

執(zhí)行一個(gè)有函數(shù)的表達(dá)式會(huì)是咋樣的呢?

先定一一個(gè)函數(shù):

type Integer interface {
    ~int | ~int8 | ~int16 | ~int32 | ~int64
}

// 求所有傳入?yún)?shù)的和
func Add[T Integer](param1, param2 T, ints ...T) T {
    sum := param1 + param2
    for _, v := range ints {
        sum += v
    }
    return sum
}

執(zhí)行有函數(shù)的表達(dá)式:6 == Add(age1, age2, age3

func Test_add(t *testing.T) {
    str := `6 == Add(age1, age2, age3)`
    env, _ := cel.NewEnv(
        cel.Variable("age1", cel.IntType),
        cel.Variable("age2", cel.IntType),
        cel.Variable("age3", cel.IntType),
        cel.Function("Add", cel.Overload(
            "Add",
            []*cel.Type{cel.IntType, cel.IntType, cel.IntType},
            cel.IntType,
            cel.FunctionBinding(func(vals ...ref.Val) ref.Val {
                var xx []int64
                for _, v := range vals {
                    xx = append(xx, v.Value().(int64))
                }
                return types.Int(Add[int64](xx[0], xx[1], xx[2:]...))
            }),
        )),
    )
    ast, iss := env.Compile(str)
    if iss.Err() != nil {
        t.Fatal(iss.Err())
    }
    prog, err := env.Program(ast)
    if err != nil {
        t.Fatal(err)
    }
    val, detail, err := prog.Eval(map[string]interface{}{"age1": 1, "age2": 2, "age3": 3})
    if err != nil {
        t.Fatal(err)
    }
    fmt.Print(detail)
    fmt.Println(val)
}

執(zhí)行步驟跟上面的一樣:

  • 首先需要申明傳入函數(shù)參數(shù)的類型 age1, age2 age3
  • 申明 Add 函數(shù),并申明函數(shù)的參數(shù)類型,以及返回結(jié)果
  • env.Compile(str) 字符串的編譯、校驗(yàn)、執(zhí)行,返回 ast
  • prog.Eval 傳入?yún)?shù)執(zhí)行并返回結(jié)果

執(zhí)行結(jié)果:

Running tool: /usr/local/go/bin/go test -timeout 10s -run ^Test_add$ github.com/demo007x/goexpr -v

=== RUN   Test_add
<nil>true
--- PASS: Test_add (0.00s)
PASS
ok      github.com/demo007x/goexpr    0.014s

場景:

一般情況下項(xiàng)目中都很少會(huì)去執(zhí)行一個(gè) CEL 的表達(dá)式,我們都會(huì)按照固定好的邏輯去編寫代碼。

目前低代碼盛行的時(shí)代,項(xiàng)目中的功能都可以自定義,這樣一個(gè)功能就需要足夠的靈活,將一些程序執(zhí)行的邏輯交給用戶去控制。

比如我們目前的項(xiàng)目中:一個(gè)復(fù)雜的流程具體要怎么執(zhí)行,需要誰去審批,需要在那一步的時(shí)候跳過等這些都是可以靈活配置每一個(gè)節(jié)點(diǎn)的邏輯條件,滿足條件的就去執(zhí)行節(jié)點(diǎn)流程,不滿足的就去跳過執(zhí)行下一個(gè)流程處理。這時(shí)候配置條件就可以使用 cel 的表達(dá)式去配置,通過表單中的多個(gè)字段的值組成一個(gè) bool 條件。

比如請假流程:請假天數(shù) <= 3 流程需要走到部門領(lǐng)導(dǎo)審批, 請假天數(shù) > 3 流程需要部門領(lǐng)導(dǎo)審批完成后繼續(xù)流轉(zhuǎn)到部門領(lǐng)導(dǎo)的上級審批。

這樣一個(gè)條件中請假天數(shù)是一個(gè)表單中某個(gè)字段的值,這樣配置條件就很靈活。這就是 cel 在我們項(xiàng)目中實(shí)際使用的例子的一部分。

cel 在 k8s中的使用

CEL 的每個(gè) Kubernetes API 字段都在 API 文檔中聲明了字段可使用哪些變量。例如,在 CustomResourceDefinitions 的 x-kubernetes-validations[i].rules 字段中,selfoldSelf 變量可用, 并且分別指代要由 CEL 表達(dá)式驗(yàn)證的自定義資源數(shù)據(jù)的前一個(gè)狀態(tài)和當(dāng)前狀態(tài)。 其他 Kubernetes API 字段可能聲明不同的變量。請查閱 API 字段的 API 文檔以了解該字段可使用哪些變量。

K8S 中 CEL 表達(dá)式示例:

規(guī)則用途
self.minReplicas <= self.replicas && self.replicas <= self.maxReplicas驗(yàn)證定義副本的三個(gè)字段被正確排序
'Available' in self.stateCounts驗(yàn)證映射中存在主鍵為 'Available' 的條目
(self.list1.size() == 0) != (self.list2.size() == 0)驗(yàn)證兩個(gè)列表中有一個(gè)非空,但不是兩個(gè)都非空
self.envars.filter(e, e.name = 'MY_ENV').all(e, e.value.matches('^[a-zA-Z]*$')驗(yàn)證 listMap 條目的 'value' 字段,其主鍵字段 'name' 是 'MY_ENV'
has(self.expired) && self.created + self.ttl < self.expired驗(yàn)證 'expired' 日期在 'create' 日期加上 'ttl' 持續(xù)時(shí)間之后
self.health.startsWith('ok')驗(yàn)證 'health' 字符串字段具有前綴 'ok'
self.widgets.exists(w, w.key == 'x' && w.foo < 10)驗(yàn)證具有鍵 'x' 的 listMap 項(xiàng)的 'foo' 屬性小于 10
type(self) == string ? self == '99%' : self == 42驗(yàn)證 int-or-string 字段是否同時(shí)具備 int 和 string 的屬性
self.metadata.name == 'singleton'驗(yàn)證某對象的名稱與特定的值匹配(使其成為一個(gè)特例)
self.set1.all(e, !(e in self.set2))驗(yàn)證兩個(gè) listSet 不相交
self.names.size() == self.details.size() && self.names.all(n, n in self.details)驗(yàn)證 'details' 映射是由 'names' listSet 中的各項(xiàng)鍵入的

思考

cel 的執(zhí)行流程都是固定的,不管是簡單的字符串還是內(nèi)嵌函數(shù)的執(zhí)行。那是不是我們就可以基于 go-cel的功能來封裝一次,將相同的邏輯代碼抽取出來。這樣使用的時(shí)候就不需要每執(zhí)行一個(gè) cel 的表達(dá)式就去寫一遍實(shí)現(xiàn)了呢?

我們分析下相同點(diǎn)和不同點(diǎn):

相同點(diǎn):

  • cel.NewEnv 初始化 env
  • env.Compile 檢測,編譯 cel 表達(dá)式
  • env.Program ast 執(zhí)行
  • prog.Eval 執(zhí)行并返回結(jié)果

不同的地方就是需要明確的標(biāo)明變量的類型,以及返回值(函數(shù)),而且參數(shù)個(gè)數(shù)不能多,也不能少,prog.Eval 傳入的實(shí)參只能多不能少,少了就會(huì)報(bào)錯(cuò)。

如果我們將需要傳遞的參數(shù)以及類型提前解析出來并動(dòng)態(tài)的傳入以上幾個(gè)步驟中,那我們的封裝是有意義的。代碼量也減少很多。哪如何抽取動(dòng)態(tài)參數(shù)以及類型呢?

以上就是在golang中使用cel的用法詳解的詳細(xì)內(nèi)容,更多關(guān)于golang中使用cel的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • Golang?中實(shí)現(xiàn)?Set的思路詳解

    Golang?中實(shí)現(xiàn)?Set的思路詳解

    本文介紹了Go中兩種set的實(shí)現(xiàn)原理,并在此基礎(chǔ)介紹了對應(yīng)于它們的兩個(gè)包簡單使用,本文介紹的非常詳細(xì),需要的朋友參考下吧
    2024-01-01
  • Go語言中slice作為參數(shù)傳遞時(shí)遇到的一些“坑”

    Go語言中slice作為參數(shù)傳遞時(shí)遇到的一些“坑”

    這篇文章主要給大家介紹了關(guān)于Go語言中slice作為參數(shù)傳遞時(shí)遇到的一些“坑”,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧。
    2018-03-03
  • Golang語言JSON解碼函數(shù)Unmarshal的使用

    Golang語言JSON解碼函數(shù)Unmarshal的使用

    本文主要介紹了Golang語言JSON解碼函數(shù)Unmarshal的使用,文中通過示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2022-01-01
  • Go 計(jì)時(shí)器使用示例全面講解

    Go 計(jì)時(shí)器使用示例全面講解

    這篇文章主要為大家介紹了Go 計(jì)時(shí)器使用示例全面講解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-09-09
  • Golang的繼承模擬實(shí)例

    Golang的繼承模擬實(shí)例

    這篇文章主要介紹了Go語言使用組合的方式實(shí)現(xiàn)多繼承的方法,實(shí)例分析了多繼承的原理與使用組合方式來實(shí)現(xiàn)多繼承的技巧,需要的朋友可以參考下,希望可以幫助到你
    2021-06-06
  • Go語言中的iota關(guān)鍵字的使用

    Go語言中的iota關(guān)鍵字的使用

    這篇文章主要為大家詳細(xì)介紹了Go語言中的iota關(guān)鍵字的相關(guān)使用,文中的示例代碼講解詳細(xì),對我們深入了解Go語言有一定的幫助,需要的可以參考下
    2023-08-08
  • 如何在Go中使用切片容量和長度

    如何在Go中使用切片容量和長度

    這篇文章主要介紹了如何在Go中使用切片容量和長度,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2019-11-11
  • 一鍵定位Golang線上服務(wù)內(nèi)存泄露的秘籍

    一鍵定位Golang線上服務(wù)內(nèi)存泄露的秘籍

    內(nèi)存泄露?別讓它拖垮你的Golang線上服務(wù)!快速掌握Go語言服務(wù)內(nèi)存泄漏排查秘籍,從此問題無處遁形,一文讀懂如何精準(zhǔn)定位與有效解決Golang應(yīng)用中的內(nèi)存問題,立即閱讀,讓性能飛升!
    2024-01-01
  • go語言 xorm框架 postgresql 的用法及詳細(xì)注解

    go語言 xorm框架 postgresql 的用法及詳細(xì)注解

    這篇文章主要介紹了go語言 xorm框架 postgresql 的用法及詳細(xì)注解,具有很好的參考價(jià)值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2020-12-12
  • golang-切片slice的創(chuàng)建方式

    golang-切片slice的創(chuàng)建方式

    這篇文章主要介紹了golang-切片slice的創(chuàng)建方式,具有很好的參考價(jià)值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2021-04-04

最新評論