golang规则引擎gengine用法案例

 更新时间:2024年01月17日 14:20:06   作者:ZHangQL  
这篇文章主要为大家介绍了golang 规则引擎gengine用法案例,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪

引言

本文受 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 : 我是一个测试用的描述信息1

rule 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的资料请关注脚本之家其它相关文章!

相关文章

  • Go语言学习笔记之golang操作MongoDB数据库

    Go语言学习笔记之golang操作MongoDB数据库

    MongoDB是Nosql中常用的一种数据库,这篇文章主要给大家介绍了关于Go语言学习笔记之golang操作MongoDB数据库的相关资料,文中通过实例代码介绍的非常详细,需要的朋友可以参考下
    2023-05-05
  • Go语言状态机的实现

    Go语言状态机的实现

    本文主要介绍了Go语言状态机的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2023-03-03
  • gin项目部署到服务器并后台启动的步骤

    gin项目部署到服务器并后台启动的步骤

    本文主要介绍了gin项目部署到服务器并后台启动的步骤,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2023-02-02
  • Golang通脉之方法详情

    Golang通脉之方法详情

    这篇文章主要介绍了Golang通脉方法,Go语言中的方法(Method)是一种作用于特定类型变量的函数。这种特定类型变量叫做接收者(Receiver)。接收者的概念就类似于,其他语言中的this或者 self,具体内容请和小编一起来学习下面文章内容吧
    2021-10-10
  • 使用go语言解析xml的实现方法(必看篇)

    使用go语言解析xml的实现方法(必看篇)

    下面小编就为大家带来一篇使用go语言解析xml的实现方法(必看篇)。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2017-06-06
  • Golang桥接模式讲解和代码示例

    Golang桥接模式讲解和代码示例

    桥接是一种结构型设计模式,可将业务逻辑或一个大类拆分为不同的层次结构,从而能独立地进行开发,本文将通过代码示例详细给大家介绍一下Golang桥接模式,需要的朋友可以参考下
    2023-06-06
  • Golang slice切片操作之切片的追加、删除、插入等

    Golang slice切片操作之切片的追加、删除、插入等

    这篇文章主要介绍了Golang slice切片操作之切片的追加、删除、插入等,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2017-11-11
  • golang atomic原子操作示例详解

    golang atomic原子操作示例详解

    这篇文章主要为大家介绍了golang atomic原子操作示例详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-09-09
  • golang实现openssl自签名双向认证的详细步骤

    golang实现openssl自签名双向认证的详细步骤

    这篇文章主要介绍了golang实现openssl自签名双向认证的详细步骤,本文分步骤给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考下吧
    2024-03-03
  • golang mapstructure库的具体使用

    golang mapstructure库的具体使用

    mapstructure用于将通用的map[string]interface{}解码到对应的 Go 结构体中,或者执行相反的操作,本文主要介绍了golang mapstructure库的具体使用,感兴趣的可以了解一下
    2023-09-09

最新评论