实现一个在线规则引擎实践| 青训营

103 阅读7分钟

实现一个在线规则引擎实践

安装Docker:

Docker是一种用于开发、交付和运行应用程序的开源平台,在容器中封装应用程序及其依赖,实现跨平台的部署

下载并运行Docker Desktop安装程序,可以从Docker官网下载安装程序

在安装完成后,打开终端(或命令提示符),运行以下命令来验证Docker是否安装成功:

shCopy code
docker --version

能够看到安装的Docker版本信息

GoLand中配置Docker:

在完成Docker的安装后,可以将其与GoLand集成,以便在GoLand中方便地构建、运行和调试容器化的应用程序

  1. 打开GoLand。
  2. 转到 File(或 文件) > Settings(或 设置)。
  3. 在设置窗口中,选择 Build, Execution, Deployment(或 构建、执行、部署) > Docker
  4. Docker 部分,点击 + 符号添加一个Docker服务器配置。
  5. 在弹出的窗口中,填写Docker服务器的名称、Docker主机地址(通常是 unix:///var/run/docker.socktcp://localhost:2375)、Docker API版本(通常为 1.41latest)等信息。
  6. 点击 OK 保存配置。

验证GoLand中的Docker配置:

在完成Docker配置后,你可以在GoLand中验证它是否正确工作:

  1. 打开GoLand。
  2. 创建一个新的Go项目或打开现有的Go项目。
  3. 点击工具栏上的 View(或 查看) > Tool Windows(或 工具窗口) > Docker,打开Docker工具窗口。
  4. 在Docker工具窗口中,你应该能够看到已经配置的Docker服务器。
  5. 通过Docker工具窗口,你可以管理Docker容器、镜像等。

实现规则引擎:

个人看法:规则引擎是一种软件组件,用于捕捉、管理和自动化业务规则。规则引擎可以使业务逻辑更具灵活性和可维护性,而不必每次更改规则时都修改代码。

Hertz框架作为一个规则引擎框架,为规则编译和执行提供了支持,使得开发者可以在不修改源代码的情况下管理和更新规则

实现基于Hertz框架的在线规则引擎涉及到以下几个主要方面:HTTP服务的搭建、数据库操作、表达式编译与执行

  • HTTP服务:用于接收前端请求,根据请求的不同路径和方法,调用相应的处理函数。
  • 数据库:用于存储和管理表达式,可以选择使用MySQL等数据库。
  • 表达式编译与执行:使用Hertz框架编译和执行规则表达式。

1. 搭建HTTP服务:

在Go语言中,可以使用标准库的net/http来搭建一个简单的HTTP服务,Go语言的net/http库提供了搭建HTTP服务的基本工具,通过http.HandleFunc函数,可以为不同的路径设置处理函数,处理特定的API请求

首先,创建一个基本的HTTP服务器和路由处理器,用于处理不同的API请求

package main
​
import (
    "encoding/json"
    "fmt"
    "net/http"
)func main() {
    http.HandleFunc("/api/engine/run", executeExpression)
    http.HandleFunc("/api/engine/exp/new", addExpression)
    http.HandleFunc("/api/engine/exp/list", listExpressions)
    http.HandleFunc("/api/engine/exp/", deleteExpression)
    http.HandleFunc("/api/engine/exp/run", executeStoredExpression)fmt.Println("Server is running on :8080")
    http.ListenAndServe(":8080", nil)
}

2. 数据库操作:

使用内存作为数据库模拟存储表达式,规则引擎中,表达式的管理是一个重要的环节。对于存储表达式,可以使用数据库来持久化存储,使用MySQL来进行增删查改操作

// 表达式结构体
type Expression struct {
    ID   int
    Exp  string
}
​
var expressionsDB = make(map[int]Expression)
var expressionIDCounter = 1func addExpression(w http.ResponseWriter, r *http.Request) {
    var exp Expression
    _ = json.NewDecoder(r.Body).Decode(&exp)
​
    // 检查是否已存在相同表达式
    for _, existingExp := range expressionsDB {
        if existingExp.Exp == exp.Exp {
            json.NewEncoder(w).Encode(map[string]interface{}{"code": 0, "message": "success", "data": map[string]int{"id": existingExp.ID}})
            return
        }
    }
​
    // 编译表达式并检查是否通过
    // 实际操作需要使用Hertz框架来编译
    // 这里简化为直接通过
    exp.ID = expressionIDCounter
    expressionsDB[exp.ID] = exp
    expressionIDCounter++
​
    json.NewEncoder(w).Encode(map[string]interface{}{"code": 0, "message": "success", "data": map[string]int{"id": exp.ID}})
}
​

Hertz框架提供了表达式的编译和执行功能,在运行时执行动态的规则

编译是将规则表达式转换为可以执行的中间表示,以便在运行时进行快速的评估。执行则是根据表达式的中间表示计算结果,判断规则是否满足条件

  1. 表达式定义: 用户定义规则表达式,包括操作数、运算符和条件等。
  2. 解析与语法分析: Hertz框架的解析器会对表达式进行解析和语法分析,将其转化为抽象语法树(AST)或其他中间表示。
  3. 中间表示生成: Hertz会将AST转换为Hertz内部的中间表示。这个中间表示可能是一种字节码、指令序列或其他形式的数据结构。
  4. 编译: Hertz会将中间表示编译成可以在特定环境中执行的形式。这通常涉及将中间表示翻译成机器码、虚拟机指令等。
  5. 参数传递: 用户提供表达式需要的参数。这些参数可能是常量、变量、函数调用结果等。
  6. 执行: 使用Hertz框架的执行引擎,执行编译生成的代码。这会计算表达式,并返回结果。
  7. 结果返回: 规则引擎将计算结果返回给用户。用户可以根据结果进行决策或进一步的处理。

3. 表达式编译与执行:

在实际情况下,使用Hertz框架来编译和执行表达式,用Go的eval库来演示表达式的执行:

// 表达式执行函数
func evaluateExpression(expression string, params map[string]interface{}) (bool, error) {
    // 在实际情况下,这里应该使用Hertz框架来编译和执行表达式
    // 这里为了示例,使用简单的eval库
    return eval(expression, params)
}
​
func executeExpression(w http.ResponseWriter, r *http.Request) {
    var data map[string]interface{}
    _ = json.NewDecoder(r.Body).Decode(&data)
​
    expression := data["exp"].(string)
    params := data["params"].(map[string]interface{})
​
    result, err := evaluateExpression(expression, params)
    if err != nil {
        json.NewEncoder(w).Encode(map[string]interface{}{"code": -1, "message": err.Error(), "data": map[string]interface{}{}})
        return
    }
​
    json.NewEncoder(w).Encode(map[string]interface{}{"code": 0, "message": "success", "data": map[string]interface{}{"result": result}})
}

基于刚刚的引擎完成词法分析、语法分析、抽象语法树的执行功能

const (
    Integer TokenType = iota
    Operator
)

定义了两个常量IntegerOperator,它们都是TokenType类型。使用iota自增器,将Integer的值设置为0,Operator的值设置为1。

type Token struct {
    Type  TokenType
    Value string
}

定义了一个名为Token的结构体,包含TypeValue两个字段。Type字段用于存储词法单元的类型,Value字段用于存储词法单元的内容。

var tokenPatterns = map[TokenType]*regexp.Regexp{
    Integer:  regexp.MustCompile(`\d+`),
    Operator: regexp.MustCompile(`[+-*/]`),
}

创建了一个映射tokenPatterns,将TokenType映射到对应的正则表达式。对于Integer类型的词法单元,使用\d+正则模式匹配多个数字字符;对于Operator类型的词法单元,使用[+-*/]正则模式匹配四则运算符。

func tokenize(input string) []Token {
    var tokens []Token
​
    for len(input) > 0 {
        matchFound := false
        for tokenType, pattern := range tokenPatterns {
            if match := pattern.FindString(input); match != "" {
                tokens = append(tokens, Token{Type: tokenType, Value: match})
                input = strings.TrimLeft(input[len(match):], " ")
                matchFound = true
                break
            }
        }
        if !matchFound {
            fmt.Printf("Unknown token: %s\n", input)
            break
        }
    }
​
    return tokens
}

定义了一个名为tokenize的函数,该函数接受一个输入字符串input,并返回一个Token类型的切片。该函数用于对输入字符串进行词法分析,将其分解为词法单元,并根据正则表达式进行匹配。

在函数内部,通过一个循环对input进行处理,直到input为空。在循环中,使用嵌套循环遍历tokenPatterns映射中的每一项。如果找到与正则表达式匹配的子串,就将匹配的子串添加到tokens切片中,并根据匹配长度更新input

如果没有找到匹配的词法单元,将输出一个错误消息并退出循环。最后,返回生成的tokens切片。

这个函数实现了将输入字符串转化为词法单元的过程,将整个输入分解为数字和运算符词法单元。