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

詳解如何使用Golang實現(xiàn)自定義規(guī)則引擎

 更新時間:2024年05月27日 09:03:29   作者:panco68120  
規(guī)則引擎的功能可以簡化為當(dāng)滿足一些條件時觸發(fā)一些操作,通常使用 DSL 自定義語法來表述,本文給大家介紹了如何使用Golang實現(xiàn)自定義規(guī)則引擎,文中有相關(guān)的代碼示例供大家參考,需要的朋友可以參考下

規(guī)則引擎的功能可以簡化為當(dāng)滿足一些條件時觸發(fā)一些操作,通常使用 DSL 自定義語法來表述。規(guī)則引擎需要先解析 DSL 語法形成語法樹,然后遍歷語法樹得到完整的語法表達(dá)式,最后執(zhí)行這些語法表達(dá)式完成規(guī)則的執(zhí)行。

本文以gengine來探討如何設(shè)計和實現(xiàn)一個自定義規(guī)則引擎。

支持的語句

為了滿足基本的業(yè)務(wù)規(guī)則需求,規(guī)則引擎應(yīng)該要支持的語句有:

邏輯與算術(shù)運算

  • 數(shù)學(xué)運算(+、-、*、/)
  • 邏輯運算(&&、||、!)
  • 比較運算(==、!=、>、<、>=、<=)

流程控制

  • 條件(IF ELSE)
  • 循環(huán) (FOR)

高級語句

  • 對象屬性訪問(對象。屬性)
  • 方法調(diào)用(func ())

規(guī)則語法的解析

規(guī)則的 DSL 語法定義應(yīng)該簡單明了,gengine 使用了開源的語法解析器 Antlr4 來定義和解析規(guī)則語法。

定義規(guī)則語法

規(guī)則的 DSL 基本語法格式如下:

rule "rulename" "rule-describtion" salience  10
begin
//規(guī)則體
end

其中規(guī)則體為具體規(guī)則語句,由上述的 邏輯與算術(shù)運算、流程控制、高級語句 組合而成。

例如,判斷為一個大額異常訂單的規(guī)則體:

if Order.Price>= 1000000 {
    return
}

編寫解析器語法

Antlr4 解析器語法定義文件后綴名為.g4,以下內(nèi)容為解析器的語法定義,解析器根據(jù)語法定義去逐行解析生成語法樹。

這里省略了一些非核心的語法定義并做了簡化,完整內(nèi)容查看 gengine.g4。

grammar gengine;

primary: ruleEntity+;
// 規(guī)則定義
ruleEntity:  RULE ruleName ruleDescription? salience? BEGIN ruleContent END;
ruleName : stringLiteral;
ruleDescription : stringLiteral;
salience : SALIENCE integer;
// 規(guī)則體
ruleContent : statements;
statements: statement* returnStmt?;

// 基本語句
statement : ifStmt | breakStmt;

expression : mathExpression
            | expression comparisonOperator expression
            | expression logicalOperator expression
            ;

mathExpression : mathExpression  mathMdOperator mathExpression
               | mathExpression  mathPmOperator mathExpression
               | expressionAtom
               | LR_BRACKET mathExpression RR_BRACKET
               ;

expressionAtom
    : functionCall
    | constant
    | variable
    ;
returnStmt : RETURN expression?;
ifStmt : IF expression LR_BRACE statements RR_BRACE elseIfStmt*  elseStmt?;
elseStmt : ELSE LR_BRACE statements RR_BRACE;

constant
    : booleanLiteral
    | integer
    | stringLiteral
    ;
functionArgs
    : (constant | variable  | functionCall | expression)  (','(constant | variable | functionCall | expression))*
    ;
integer : MINUS? INT;
stringLiteral: DQUOTA_STRING;
booleanLiteral : TRUE | FALSE;
functionCall : SIMPLENAME LR_BRACKET functionArgs? RR_BRACKET;
variable :  SIMPLENAME | DOTTEDNAME;
mathPmOperator : PLUS | MINUS;
mathMdOperator : MUL | DIV;
comparisonOperator : GT | LT | GTE | LTE | EQUALS | NOTEQUALS;

解析器生成語法樹

如,判斷為一個大額異常訂單的規(guī)則:

rule "order-large-price" "訂單大額金額" salience 10
begin
    if Order.Price >= 1000000 {
        return
    }
end

語法解析器解析之后,生成語法樹:

遍歷語法樹生成語句表達(dá)式

解析器生成語法樹之后,只需要遍歷語法樹即可得到完整的語句表達(dá)式。Antlr4 解析器會生成 Listener 接口,這些接口在遍歷語法樹時會被調(diào)用。

type gengineListener interface {
    antlr.ParseTreeListener
    // 省略了一些只列舉了部分方法
    // EnterRuleEntity is called when entering the ruleEntity production.
    EnterRuleEntity(c *RuleEntityContext)
    // ExitRuleEntity is called when exiting the ruleEntity production.
    ExitRuleEntity(c *RuleEntityContext)
    // EnterRuleContent is called when entering the ruleContent production.
    EnterRuleContent(c *RuleContentContext)
    // ExitRuleContent is called when exiting the ruleContent production.
    ExitRuleContent(c *RuleContentContext)
    // EnterStatement is called when entering the statement production.
    EnterStatement(c *StatementContext)
    // ExitStatement is called when exiting the statement production.
    ExitStatement(c *StatementContext)
    // EnterIfStmt is called when entering the ifStmt production.
    EnterIfStmt(c *IfStmtContext)
    // ExitIfStmt is called when exiting the ifStmt production.
    ExitIfStmt(c *IfStmtContext)
    // EnterExpression is called when entering the expression production.
    EnterExpression(c *ExpressionContext)
    // ExitExpression is called when exiting the expression production.
    ExitExpression(c *ExpressionContext)
    // EnterInteger is called when entering the integer production.
    EnterInteger(c *IntegerContext)
    // ExitInteger is called when exiting the integer production.
    ExitInteger(c *IntegerContext)
}

可以發(fā)現(xiàn)在遍歷語法樹時,每個節(jié)點都有 EnterXXX() 和 ExitXXX() 方法存在,是成對出現(xiàn)的。

因此要遍歷語法樹只需要實現(xiàn) gengineListener 接口即可,gengine 巧妙的引入結(jié)構(gòu),遍歷完語法樹后(樹的遞歸遍歷就是進(jìn)棧出棧過程),就得到了完整的規(guī)則語句表達(dá)式。這里只列舉部分方法,完整實現(xiàn)見 gengine_parser_listener。

type GengineParserListener struct {
    parser.BasegengineListener

    KnowledgeContext *base.KnowledgeContext
    Stack            *stack.Stack
}

func (g *GengineParserListener) EnterRuleEntity(ctx *parser.RuleEntityContext) {
    if len(g.ParseErrors) > 0 {
        return
    }
    entity := &base.RuleEntity{
        Salience: 0,
    }
    g.ruleName = ""
    g.ruleDescription = ""
    g.salience = 0
    g.Stack.Push(entity)
}

func (g *GengineParserListener) ExitRuleEntity(ctx *parser.RuleEntityContext) {
    if len(g.ParseErrors) > 0 {
        return
    }
    entity := g.Stack.Pop().(*base.RuleEntity)
    g.KnowledgeContext.RuleEntities[entity.RuleName] = entity
}

gengine 通過解析器解析規(guī)則內(nèi)容之后,規(guī)則的數(shù)據(jù)結(jié)構(gòu)如下:

全局的 hashmap 以規(guī)則名為 key,規(guī)則體為 value,規(guī)則體中的 ruleContent 為該規(guī)則所有的語句表達(dá)式列表,列表中的值指向具體的語句表達(dá)式實體,語句表達(dá)式實體由 邏輯與算術(shù)運算、流程控制(IF、FOR)等基本語句組成。

規(guī)則語法的執(zhí)行

其實遍歷語法樹的過程中,將規(guī)則的執(zhí)行邏輯也放入 ExitXXX() 方法,這樣就能一并完成規(guī)則的解析和執(zhí)行。但是 gengine 沒有這么做,而是將規(guī)則的解析和執(zhí)行解耦,因為規(guī)則的解析往往只需要初始化一次,或者在規(guī)則有變更時熱更新解析,而規(guī)則的執(zhí)行則是在需要校驗規(guī)則的時候。

從 gengine 的規(guī)則數(shù)據(jù)結(jié)構(gòu)可知,只需要遍歷全局的 hashmap,即可按順序執(zhí)行所有的規(guī)則(順序模式),執(zhí)行每一個規(guī)則后會通過addResult()方法記錄執(zhí)行結(jié)果:

// 順序模式
func (g *Gengine) Execute(rb *builder.RuleBuilder, b bool) error {
    for _, r := range rb.Kc.RuleEntities {
        v, err, bx := r.Execute(rb.Dc)
        if bx {
            // 記錄每個規(guī)則執(zhí)行結(jié)果
            g.addResult(r.RuleName, v)
        }
    }
    // 省略部分
    ...
}

對于某一個規(guī)則的執(zhí)行,則會去遍歷規(guī)則體 ruleContent 的所有語句表達(dá)式列表,然后按順序去執(zhí)行該規(guī)則下的所有語句表達(dá)式:

func (s *Statements) Evaluate(dc *context.DataContext, Vars map[string]reflect.Value) (reflect.Value, error, bool) {
    for _, statement := range s.StatementList {
        v, err, b := statement.Evaluate(dc, Vars)
        if err != nil {
            return reflect.ValueOf(nil), err, false
        }

        if b {
            // return的情況不需要繼續(xù)執(zhí)行
            return v, nil, b
        }
    if s.ReturnStatement != nil {
        return s.ReturnStatement.Evaluate(dc, Vars)
    }
    return reflect.ValueOf(nil), nil, false
}

gengine 為每個語句類型都實現(xiàn)了 Evaluate() 方法,這里只討論 IF 語句的執(zhí)行:

type IfStmt struct {
    Expression     *Expression
    StatementList  *Statements
    ElseIfStmtList []*ElseIfStmt
    ElseStmt       *ElseStmt
}

func (i *IfStmt) Evaluate(dc *context.DataContext, Vars map[string]reflect.Value) (reflect.Value, error, bool) {
    // 執(zhí)行條件表達(dá)式
    it, err := i.Expression.Evaluate(dc, Vars)
    if err != nil {
        return reflect.ValueOf(nil), err, false
    }
    // 執(zhí)行條件為真時的語句
    if it.Bool() {
        if i.StatementList == nil {
            return reflect.ValueOf(nil), nil, false
        } else {
            return i.StatementList.Evaluate(dc, Vars)
        }
    }

    return reflect.ValueOf(nil), nil, false
}

其中條件表達(dá)式Expression.Evaluate()為計算條件表達(dá)式的值:

func (e *Expression) Evaluate(dc *context.DataContext, Vars map[string]reflect.Value) (reflect.Value, error) {
    // 原子表達(dá)式
    var atom reflect.Value
    if e.ExpressionAtom != nil {
        evl, err := e.ExpressionAtom.Evaluate(dc, Vars)
        if err != nil {
            return reflect.ValueOf(nil), err
        }
        atom = evl
    }
    
    // 比較操作
    if e.ComparisonOperator != "" {
        // 計算左值
        lv, err := e.ExpressionLeft.Evaluate(dc, Vars)
        if err != nil {
            return reflect.ValueOf(nil), err
        }
        // 計算右值
        rv, err := e.ExpressionRight.Evaluate(dc, Vars)
        if err != nil {
            return reflect.ValueOf(nil), err
        }
        // 省略了類型轉(zhuǎn)化
        switch e.ComparisonOperator {
        case "==":
            b = reflect.ValueOf(lv == rv)
            break
        case "!=":
            b = reflect.ValueOf(lv != rv)
            break
        case ">":
            b = reflect.ValueOf(lv > rv)
            break
        case "<":
            b = reflect.ValueOf(lv < rv)
            break
        case ">=":
            b = reflect.ValueOf(lv >= rv)
            break
        case "<=":
            b = reflect.ValueOf(lv <= rv)
            break
        }
    }
}

遞歸執(zhí)行到ExpressionAtom.Evaluate()原子表達(dá)式時,就可以得到該原子表達(dá)式的值以結(jié)束遞歸:

func (e *ExpressionAtom) Evaluate(dc *context.DataContext, Vars map[string]reflect.Value) (reflect.Value, error) {
    if len(e.Variable) > 0 {
        // 是變量則取變量值,通過反射獲取注入的自定義對象值
        return dc.GetValue(Vars, e.Variable)
    } else if e.Constant != nil {
        // 是常量就返回值
        return e.Constant.Evaluate(dc, Vars)
    }
    // 省略部分
}

支持自定義對象注入

在上下文中注入自定義對象后,就可以在規(guī)則中使用注入的對象。使用例子:

// 規(guī)則體
rule "test-object" "測試自定義對象" salience 10
begin
    // 訪問自定義對象Order
    if Order.Price >= 1000000 {
        return
    }
end

// 注入自定義對象Order
dataContext := gctx.NewDataContext()
dataContext.Add("Order", Order)

現(xiàn)在來看下 gengine 的具體實現(xiàn),主要是使用反射特性:

func (dc *DataContext) Add(key string, obj interface{}) {
    dc.lockBase.Lock()
    defer dc.lockBase.Unlock()
    dc.base[key] = reflect.ValueOf(obj)
}

gengine 解析規(guī)則時會將自定義對象標(biāo)記為variable類型,通過 GetValue() 獲取自定義對象屬性值:

// 獲取變量值
func (dc *DataContext) GetValue(Vars map[string]reflect.Value, variable string) (reflect.Value, error) {
    if strings.Contains(variable, ".") {
        // 對象a.b
        structAndField := strings.Split(variable, ".")
        if len(structAndField) == 2 {
            a := structAndField[0]
            b := structAndField[1]
            // 獲取注入的對象
            dc.lockBase.Lock()
            v, ok := dc.base[a]
            dc.lockBase.Unlock()
            if ok {
                return core.GetStructAttributeValue(v, b)
            }
        }
    }
}

// 反射獲取對象屬性值
func GetStructAttributeValue(obj reflect.Value, fieldName string) (reflect.Value, error) {
    stru := obj
    var attrVal reflect.Value
    if stru.Kind() == reflect.Ptr {
        attrVal = stru.Elem().FieldByName(fieldName)
    } else {
        attrVal = stru.FieldByName(fieldName)
    }
    return attrVal, nil
}

支持自定義方法注入

同樣在上下文中注入自定義方法后,也可以在規(guī)則中使用注入的方法。使用例子:

// 規(guī)則體
rule "test-func" "測試自定義方法" salience 10
begin
    // 自定義方法GetCount獲取指標(biāo)數(shù)據(jù)(患者當(dāng)天的訂單數(shù)量)
    num = GetCount("order-patient-id", Order.PatientId)
    if num >= 5 {
        return
    }
end

// 注入自定義方法GetCount
dataSvc := s.indicatorDao.NewDataService(ctx)
dataContext := gctx.NewDataContext()
dataContext.Add("GetCount", dataSvc.GetCount)

gengine 自定義方法的注入也是使用反射來實現(xiàn),自定義方法的注入同自定義對象一樣也是使用 Add() 方法注入。

gengine 解析規(guī)則時會將自定義方法標(biāo)記為functionCall類型:

func (dc *DataContext) ExecFunc(Vars map[string]reflect.Value, funcName string, parameters []reflect.Value) (reflect.Value, error) {
    // 獲取注入的方法
    dc.lockBase.Lock()
    v, ok := dc.base[funcName]
    dc.lockBase.Unlock()
    if ok {
        args := core.ParamsTypeChange(v, parameters)
        // 調(diào)用方法
        res := v.Call(args)
        raw, e := core.GetRawTypeValue(res)
        if e != nil {
            return reflect.ValueOf(nil), e
        }
        return raw, nil
    }
}

支持并發(fā)執(zhí)行

通常情況下順序模式執(zhí)行即可滿足要求,但是當(dāng)規(guī)則數(shù)量比較大時,順序執(zhí)行的耗時就會比較長。

規(guī)則引擎在執(zhí)行所有規(guī)則的時候,其實是遍歷全局的 hashmap 然后再順序執(zhí)行每一個規(guī)則,由于每個規(guī)則之間沒有依賴關(guān)系,因此可以用每一個規(guī)則一個協(xié)程來并發(fā)執(zhí)行。

func (g *Gengine) ExecuteConcurrent(rb *builder.RuleBuilder) error {
    var wg sync.WaitGroup
    wg.Add(len(rb.Kc.RuleEntities))
    for _, r := range rb.Kc.RuleEntities {
        rr := r
        // 協(xié)程并發(fā)
        go func() {
            v, e, bx := rr.Execute(rb.Dc)
            if bx {
                g.addResult(rr.RuleName, v)
            }
            wg.Done()
        }()
    }
    wg.Wait()
    // 省略部分
}

使用場景

有了規(guī)則引擎之后,很多在業(yè)務(wù)代碼中的 if-else、switch 硬編碼,都能抽象為規(guī)則并使用規(guī)則引擎,這樣能通過配置規(guī)則代替硬編碼,能極大地縮短變更上線時間。

業(yè)務(wù)風(fēng)控

通過業(yè)務(wù)數(shù)據(jù)分析,可以抽象出用戶異常行為的規(guī)則:

然后,風(fēng)控系統(tǒng)在判斷是否為風(fēng)險操作時,只需要規(guī)則引擎加載并執(zhí)行風(fēng)控規(guī)則,即可得到結(jié)果。想要提高風(fēng)控系統(tǒng)的準(zhǔn)確性,只需要不斷地迭代完善風(fēng)控規(guī)則。

規(guī)則引擎在業(yè)務(wù)風(fēng)控的實踐,可以參考 基于準(zhǔn)實時規(guī)則引擎的業(yè)務(wù)風(fēng)控實踐。

運營活動

拿最常見的抽獎和做任務(wù) 2 種運營活動來說,都可以將具體活動邏輯抽象為業(yè)務(wù)規(guī)則:① 抽獎,不同的人&不同的場景對應(yīng)不同的獎池(中獎概率與獎品集合規(guī)則);② 做任務(wù),任務(wù)領(lǐng)取規(guī)則、任務(wù)完成指標(biāo)動態(tài)可配(任務(wù)規(guī)則);

內(nèi)容分發(fā)

針對某些特定的用戶或者某種場景的用戶,下發(fā)特定的展示內(nèi)容或者推送短信等觸達(dá)消息,都可以將這些特定用戶的邏輯梳理為內(nèi)容分發(fā)規(guī)則。

以上就是詳解如何使用Golang實現(xiàn)自定義規(guī)則引擎的詳細(xì)內(nèi)容,更多關(guān)于Golang自定義規(guī)則引擎的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • Go語言運算符案例講解

    Go語言運算符案例講解

    這篇文章主要介紹了Go語言運算符案例講解,本篇文章通過簡要的案例,講解了該項技術(shù)的了解與使用,以下就是詳細(xì)內(nèi)容,需要的朋友可以參考下
    2021-07-07
  • Go底層channel實現(xiàn)原理及示例詳解

    Go底層channel實現(xiàn)原理及示例詳解

    這篇文章主要介紹了Go底層channel實現(xiàn)原理及示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-08-08
  • Go并發(fā):使用sync.WaitGroup實現(xiàn)協(xié)程同步方式

    Go并發(fā):使用sync.WaitGroup實現(xiàn)協(xié)程同步方式

    這篇文章主要介紹了Go并發(fā):使用sync.WaitGroup實現(xiàn)協(xié)程同步方式,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2021-05-05
  • ???????Golang實現(xiàn)RabbitMQ中死信隊列幾種情況

    ???????Golang實現(xiàn)RabbitMQ中死信隊列幾種情況

    本文主要介紹了???????Golang實現(xiàn)RabbitMQ中死信隊列幾種情況,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2023-03-03
  • GoLang中的互斥鎖Mutex和讀寫鎖RWMutex使用教程

    GoLang中的互斥鎖Mutex和讀寫鎖RWMutex使用教程

    RWMutex是一個讀/寫互斥鎖,在某一時刻只能由任意數(shù)量的reader持有或者一個writer持有。也就是說,要么放行任意數(shù)量的reader,多個reader可以并行讀;要么放行一個writer,多個writer需要串行寫
    2023-01-01
  • golang用melody搭建輕量的websocket服務(wù)的示例代碼

    golang用melody搭建輕量的websocket服務(wù)的示例代碼

    在Go中,可以使用gin和melody庫來搭建一個輕量級的WebSocket服務(wù),gin是一個流行的Web框架,而melody是一個用于處理WebSocket的庫,本文給大家演示如何使用gin和melody搭建WebSocket服務(wù),感興趣的朋友一起看看吧
    2023-10-10
  • golang?slice中常見性能優(yōu)化手段總結(jié)

    golang?slice中常見性能優(yōu)化手段總結(jié)

    這篇文章主要為大家詳細(xì)一些Golang開發(fā)中常用的slice關(guān)聯(lián)的性能優(yōu)化手段,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下
    2024-10-10
  • Go語言的常量、枚舉、作用域示例詳解

    Go語言的常量、枚舉、作用域示例詳解

    這篇文章主要介紹了Go語言的常量、枚舉、作用域,接下來,我們將詳細(xì)了解 Go 的變量作用域規(guī)則以及這些規(guī)則如何影響代碼編寫,需要的朋友可以參考下
    2024-07-07
  • Go語言學(xué)習(xí)之goroutine詳解

    Go語言學(xué)習(xí)之goroutine詳解

    Goroutine是建立在線程之上的輕量級的抽象。它允許我們以非常低的代價在同一個地址空間中并行地執(zhí)行多個函數(shù)或者方法,這篇文章主要介紹了Go語言學(xué)習(xí)之goroutine的相關(guān)知識,需要的朋友可以參考下
    2020-02-02
  • golang獲取當(dāng)前時間、時間戳和時間字符串及它們之間的相互轉(zhuǎn)換方法

    golang獲取當(dāng)前時間、時間戳和時間字符串及它們之間的相互轉(zhuǎn)換方法

    這篇文章主要介紹了golang獲取當(dāng)前時間、時間戳和時間字符串及它們之間的相互轉(zhuǎn)換,本文通過實例代碼給大家介紹的非常詳細(xì),感興趣的朋友一起看看吧
    2025-04-04

最新評論