golang规则引擎gengine用法案例
作者:ZHangQL
引言
本文受 golang面试经典讲解
的[Go工具库]B 站新一代 golang 规则引擎gengine启示, 文中的基本用法和作者的细节都基本提到了,大家关心的可以去看下设计文档及作者对比的gopher_lua
的对比
本文主要下作者的几个案例和用法
Part1 基本使用
package main import ( "fmt" "github.com/bilibili/gengine/builder" "github.com/bilibili/gengine/context" "github.com/bilibili/gengine/engine" "github.com/sirupsen/logrus" "time" ) func main() { TestMulti() } type User struct { Name string Age int64 Male bool } func (u *User) GetNum(i int64) int64 { return i } func (u *User) Print(s string) { fmt.Println(s) } func (u *User) Say() { fmt.Println("hello world") } // 定义规则 const rule1 = ` rule "name test" "i can" salience 0 begin if 7 == User.GetNum(7){ User.Age = User.GetNum(89767) + 10000000 User.Print("6666") }else{ User.Name = "yyyy" } end ` func TestMulti() { user := &User{ Name: "Calo", Age: 0, Male: true, } dataContext := context.NewDataContext() //注入初始化的结构体 dataContext.Add("User", user) //init rule engine ruleBuilder := builder.NewRuleBuilder(dataContext) start1 := time.Now() //构建规则 err := ruleBuilder.BuildRuleFromString(rule1) //string(bs) end1 := time.Now() logrus.Infof("rules num:%d, load rules cost time:%d", len(ruleBuilder.Kc.RuleEntities), end1.Sub(start1).Milliseconds()) if err != nil { logrus.Errorf("err:%s ", err) } else { eng := engine.NewGengine() start := time.Now().UnixNano() //执行规则 err := eng.Execute(ruleBuilder, true) println(user.Age) end := time.Now().UnixNano() if err != nil { logrus.Errorf("execute rule error: %v", err) } logrus.Infof("execute rule cost %d ns", end-start) logrus.Infof("user.Age=%d,Name=%s,Male=%t", user.Age, user.Name, user.Male) } }
INFO[0000] rules num:1, load rules cost time:5
6666
10089767
INFO[0000] execute rule cost 97000 ns
INFO[0000] user.Age=10089767,Name=Calo,Male=true
Part2 @name
在规则体中使用@name,指代的是当前规则名,@name在规则执行时,会被解析为规则名字符串(规则内的名字感知)
package main import ( "fmt" "github.com/bilibili/gengine/builder" "github.com/bilibili/gengine/context" "github.com/bilibili/gengine/engine" "time" ) func PrintName(name string) { fmt.Println(name) } /* * use '@name',you can get rule name in rule content */ const atNameRule = ` rule "测试规则名称1" "rule desc" begin va = @name PrintName(va) PrintName(@name) end rule "rule name" "rule desc" begin va = @name PrintName(va) PrintName(@name) end ` func exec() { dataContext := context.NewDataContext() dataContext.Add("PrintName", PrintName) //init rule engine ruleBuilder := builder.NewRuleBuilder(dataContext) //resolve rules from string start1 := time.Now().UnixNano() err := ruleBuilder.BuildRuleFromString(atNameRule) end1 := time.Now().UnixNano() println(fmt.Sprintf("rules num:%d, load rules cost time:%d ns", len(ruleBuilder.Kc.RuleEntities), end1-start1)) if err != nil { panic(err) } eng := engine.NewGengine() start := time.Now().UnixNano() // true: means when there are many rules, if one rule execute error,continue to execute rules after the occur error rule err = eng.Execute(ruleBuilder, true) end := time.Now().UnixNano() if err != nil { panic(err) } println(fmt.Sprintf("execute rule cost %d ns", end-start)) } func main() { exec() }
rules num:2, load rules cost time:2820000 ns
测试规则名称1
测试规则名称1
rule name
rule name
execute rule cost 72000 ns
@name 主要是获取规则的名字的
Part3 @id
在规则体中使用@id含义是,如果当前的规则名是可以转化为整数的字符串,则@id就是规则名的整数值,如果规则名字符串不可转化为整数,则@id的值为0.这个是为了方便用户以规则名作为整型参数
package main import ( "fmt" "github.com/bilibili/gengine/builder" "github.com/bilibili/gengine/context" "github.com/bilibili/gengine/engine" ) /* * use '@id',you can get rule name in rule content */ const atIDRule = ` rule "测试规则名称1" "rule desc" salience 10 begin println(@id) end rule " 100 " "rule desc" salience 20 begin x = @id println(x) end ` func TestAtId() { dataContext := context.NewDataContext() dataContext.Add("println", fmt.Println) //init rule engine ruleBuilder := builder.NewRuleBuilder(dataContext) //resolve rules from string err := ruleBuilder.BuildRuleFromString(atIDRule) if err != nil { panic(err) } eng := engine.NewGengine() err = eng.Execute(ruleBuilder, false) if err != nil { panic(err) } } type Data struct { M map[string]string } func (d *Data) exe() { println("hhhh") } func main() { TestAtId() }
100 0
Part4 @desc语法
在规则体内获知当前规则的描述信息,类型为字符串
package main import ( "fmt" "github.com/bilibili/gengine/builder" "github.com/bilibili/gengine/context" "github.com/bilibili/gengine/engine" "time" ) /* * use '@desc',you can get rule description in rule content */ const atDescRule = ` rule "rule name 1" "我是一个测试用的描述信息1" salience 100 begin desc = @desc Print(desc) Print(@name + " : " + @desc) end rule "rule name 2" //"我是描述,desc" salience 10 begin desc = @desc Print(desc) Print(@name + " : " + @desc) end ` func main() { dataContext := context.NewDataContext() // dataContext.Add("Print", PrintName) dataContext.Add("Print", fmt.Println) //init rule engine ruleBuilder := builder.NewRuleBuilder(dataContext) //resolve rules from string start1 := time.Now().UnixNano() err := ruleBuilder.BuildRuleFromString(atDescRule) end1 := time.Now().UnixNano() println(fmt.Sprintf("rules num:%d, load rules cost time:%d ns", len(ruleBuilder.Kc.RuleEntities), end1-start1)) if err != nil { panic(err) } eng := engine.NewGengine() // true: means when there are many rules, if one rule execute error,continue to execute rules after the occur error rule err = eng.Execute(ruleBuilder, true) if err != nil { panic(err) } }
rules num:2, load rules cost time:3172000 ns
我是一个测试用的描述信息1
rule name 1 : 我是一个测试用的描述信息1rule name 2 :
注意如果有//是注释 无法解析到
Part5 sal
在规则体内获知当前规则的优先级信息,类型为int64
package main import ( "fmt" "github.com/bilibili/gengine/builder" "github.com/bilibili/gengine/context" "github.com/bilibili/gengine/engine" "time" ) func main() { dataContext := context.NewDataContext() dataContext.Add("println", fmt.Println) //init rule engine ruleBuilder := builder.NewRuleBuilder(dataContext) err := ruleBuilder.BuildRuleFromString(` rule "1" salience 10 begin println(@sal) end rule "2" begin println(@sal) end `) if err != nil { panic(err) } eng := engine.NewGengine() start := time.Now().UnixNano() // true: means when there are many rules, if one rule execute error,continue to execute rules after the occur error rule err = eng.Execute(ruleBuilder, true) end := time.Now().UnixNano() if err != nil { panic(err) } println(fmt.Sprintf("execute rule cost %d ns", end-start)) }
10 0 execute rule cost 120000 ns
Part6 注释
支持规则内的单行注释,注释以双斜杠(//)开头
Part7 自定义变量
用户自定义变量无需申明类型 规则内定义的变量,只对当前规则可见,对其他规则不可见(局部变量) 使用dataContext注入的(变量)数据,对加载到gengine中的所有规则均可见(全局变量)
Part8 报错时行号提示
gengine支持的语法,是完整的DSL语法(也可以当作是一门完整的语言),gengine规则执行出错时,gengine会指出具体的出错在哪一行.尽量帮助用户在使用gengine的每一个流程细节上,都有丝滑体验
package main import ( "fmt" "github.com/bilibili/gengine/builder" "github.com/bilibili/gengine/context" "github.com/bilibili/gengine/engine" ) var lineNumberRules = ` rule "line_number" "when execute error,gengine will give out error" begin //Println("golang", "hello", "world" ) //取消斜杠注释,依次测试不同的报错情况 //if Println("golang", "hello") == 100 { // Println("golang", "hello") //} ms.X() end ` type MyStruct struct { } func (m *MyStruct) XX(s string) { println("XX") } func Println(s1, s2 string) bool { println(s1, s2) return false } func main() { dataContext := context.NewDataContext() //注入自定义函数 dataContext.Add("Println", Println) ms := &MyStruct{} dataContext.Add("ms", ms) ruleBuilder := builder.NewRuleBuilder(dataContext) e1 := ruleBuilder.BuildRuleFromString(lineNumberRules) if e1 != nil { panic(e1) } eng := engine.NewGengine() // true: means when there are many rules, if one rule execute error,continue to execute rules after the occur error rule e2 := eng.Execute(ruleBuilder, true) if e2 != nil { println(fmt.Sprintf("%+v", e2)) } }
[rule: "line_number" executed, error:
line 12, column 0, code: ms.X(), NOT FOUND Function: X ]
Part9 多级调用支持
现在已经支持了A.B.C形式的三级调用,但三级以上的调用则不支持 其他细节说明:
1.当语法是C,或者a=C, 则C可以为具体值、变量、函数或方法、结构体(指针)、map、slice、array等, 具体的如 a=100, a = Mp["hello"], a = x, a = getMessage(p1,p2..)等
2.当语法是A.C,或者a=A.C, 则A必须为结构体(指针), C同上, 具体如a = A.Mp["hello"], a = A.Field1, a = A.GetMessage(p1,p2..)等
3.当语法是A.B.C,或者a=A.B.C, 则A和B必须为结构体(指针),C同上, 具体如 a = A.B.Mp["hello"], a =A.B.Field1, a= A.B.GetMessage(p1, p2..)等
那么,如果语法为A.B.C时, A.Mp["hello"].C 这种语法是不合法的
package main import ( "fmt" "github.com/bilibili/gengine/builder" "github.com/bilibili/gengine/context" "github.com/bilibili/gengine/engine" ) type B struct { Name string Mp map[string]string Ar [5]string Sl []int } func (b *B) Meth(s string) { println("--->", s) } func main() { type A struct { N string Ma map[string]string B *B } rule := ` rule "three level call" begin conc{ A.B.Name = "xiaoMing" A.B.Mp["hello"] = "world" A.B.Ar[1] = "Calo" A.B.Sl[2] = 3 x = A.B.Sl[0] A.B.Meth(A.B.Ar[1]) A.N = "kakaka" A.Ma["a"] = "b" } println(A.B.Name, A.B.Mp["hello"], A.B.Ar[1], A.B.Sl[2], x, A.N, A.Ma["a"]) if A.B.Sl[0] == 0 { println(true) } end ` b := B{ Name: "", Mp: make(map[string]string), Ar: [5]string{}, Sl: make([]int, 6), } pA := &A{ N: "", Ma: make(map[string]string), B: &b, } dataContext := context.NewDataContext() dataContext.Add("println", fmt.Println) dataContext.Add("A", pA) ruleBuilder := builder.NewRuleBuilder(dataContext) e := ruleBuilder.BuildRuleFromString(rule) if e != nil { panic(e) } gengine := engine.NewGengine() e = gengine.Execute(ruleBuilder, true) if e != nil { panic(e) } println(pA.B.Name, pA.B.Mp["hello"], pA.B.Ar[1], pA.B.Sl[2]) }
---> Calo
xiaoMing world Calo 3 0 kakaka b
true
xiaoMing world Calo 3
以上就是golang 规则引擎gengine用法案例的详细内容,更多关于golang 规则引擎gengine的资料请关注脚本之家其它相关文章!