这是我参与「第五届⻘训营 」笔记创作活动的第6天。今天是实践课程,学习利用Go语言构建规则引擎,这篇笔记记录了课后作业,结合前面课程的内容课后实现一个在线版本的规则引擎。
项目要求
使用Hertz框架开发一个HTTP服务,服务使用mysql,支持表达式的增删查改和编译执行。 并实现项目中预留的接口
项目目录
➜ young_engine main ✓ tree
.
├── README.md
├── biz
│ ├── dal
│ │ ├── dal.go
│ │ ├── expression.go
│ │ ├── expression_test.go
│ │ └── sql
│ │ └── init.sql
│ └── handler
│ ├── base.go
│ ├── compiler.go
│ ├── compiler_test.go
│ ├── engine.go
│ ├── expressions.go
│ └── ping.go
├── compiler
│ ├── Engine.g4
│ ├── builder.go
│ ├── lexical.go
│ ├── parser.go
│ ├── parser_test.go
│ ├── scanner.go
│ └── scanner_test.go
├── docker-compose.yml
├── executor
│ ├── ast.go
│ ├── operator.go
│ ├── svg.go
│ ├── symbol.go
│ ├── type.go
│ └── type_checker.go
├── go.mod
├── go.sum
├── image
│ ├── fixed.jpg
│ ├── fixed_point.jpg
│ ├── lexcer_status.jpg
│ ├── node.svg
│ └── precedence.jpg
├── main.go
├── setup.sh
├── test
│ ├── handle_test.go
│ └── rune_test.go
└── token
├── kind.go
├── kind_test.go
├── lexer.go
└── token.go
我们要实现的代码在biz/handler文件夹的expressions.go中。
启动DB
使用docker-compose运行mysql作为DB,这是我们使用gorm操作数据的平台。
docker-compose up
运行项目
go run ./main.go
测试工具使用postman。
接口实现
查询表达式:
查询数据库中所有的表达式
GET api/engine/exp/list- Response
{
"code": 0,
"message": "success",
"data": [
{
"id": 1,
"exp": "uid > 0"
}
]
}
此接口为查询接口,使用dal包中的GetAllExpression()函数可从数据库中获取到查询的所有表达式,代码如下:
func HandleGetAllExpression(ctx context.Context, c *app.RequestContext) {
exps, err := dal.GetAllExpression()
if err != nil {
BindResp(c, ParamErrCode, err.Error(), nil)
return
}
BindResp(c, SuccessCode, SuccessMsg, exps)
}
从数据库中获取数据之后将获取结果map[string]interface{}填入返回报文body返回给客户端。
直接表达式执行:
请求参数为待执行的表达式和表达式中参数的值,并输出编译结果
实时编译并执行结果,不需要写入DB中
POST api/engine/run- Request
{
"exp": "uid == 12345 && did > 0",
"params": {
"uid": 123456,
"did": 0
}
}
复制代码
- Response
{
"code": 0,
"message": "success",
"data": { // 执行结果
"result": true
}
}
实现代码如下:
func HandleRunExpression(ctx context.Context, c *app.RequestContext) {
var req RunRequest
if err := c.Bind(&req); err != nil {
BindResp(c, ParamErrCode, err.Error(), nil)
return
}
params, _ := getParams(req.Params)
exps, _ := dal.GetExpressionByID(uint(req.Exp_id))
if len(exps.Exp) == 0 {
msg := fmt.Sprintf("exp id %d not exist", req.Exp_id)
BindResp(c, DatabaseErrCode, msg, nil)
return
}
evaluatedExp, err := Compiler(exps.Exp)
if err != nil {
BindResp(c, CompileErrCode, err.Error(), nil)
return
}
err = evaluatedExp.Eval(params)
if err != nil {
BindResp(c, RuleExecErrCode, err.Error(), nil)
return
}
resp, _ := evaluatedExp.GetVal()
BindResp(c, SuccessCode, SuccessMsg, resp)
}
首先使用框架中实现的Bind机制,将请求body中json:exp表达式转换为自定义结构体
type RunRequest struct {
Exp_id int `json:exp_id`
Params map[string]interface{} `json:"parmas"`
}
根据获取结构体中Exp_id,使用dal包中getParams(id uint)函数根据id获取存在于DB中的表达式。如果获取正确使用先前构建好的规则引擎编译表达式,并执行结果,最终将结果存入返回body中回复给Client。
新增表达式:
新增一条表达式到DB中,并返回表达式在DB中的ID
需要检测表达式是否已经存在,如果已经存在,直接返回表达式的ID
需要检测表达式是否合法(编译是否通过) ,如果编译失败,返回错误码 20001和编译错误
POST api/engine/exp/new- Request
{
"exp": "uid == 12345 && did > 0",
}
复制代码
- Response
{
"code": 0,
"message": "success",
"data": { // 表达式ID
"id": 1
}
}
// 编译失败时
{
"code": -1,
"message": "compile error: xxxxx", // 编译失败的信息
"data": { // 表达式ID
"id": 0
}
}
实现代码如下:
func HandleAddExpression(ctx context.Context, c *app.RequestContext) {
var req AddRequst
if err := c.Bind(&req); err != nil {
BindResp(c, ParamErrCode, err.Error(), nil)
return
}
resp := map[string]interface{}{
"id": 0,
}
_, err := Compiler(req.Exp)
if err != nil {
BindResp(c, -1, err.Error(), resp)
return
}
exps, err := dal.AddExpression(req.Exp)
if err != nil {
BindResp(c, DatabaseErrCode, err.Error(), resp)
return
}
resp["id"] = exps.ID
BindResp(c, SuccessCode, SuccessMsg, resp)
}
首先使用框架中实现的Bind机制,将请求body中json:exp表达式转换为自定义结构体
type AddRequst struct {
Exp string `json:exp`
}
将获取到的表达式利用dal包中AddExpression(exp string)函数存入DB中,并将结果返回客户端。
删除表达式:
根据ID删除表达式,表达式不存在时返回错误码20002 , 和错误信息
删除成功返回被删除的表达式信息
DELETE api/engine/exp/:id- Response
// 删除成功时
{
"code": 0,
"message": "success",
"data": { // 表达式ID
"id": 1,
"exp": "uid > 0"
}
}
// 删除失败时
{
"code": -1,
"message": "exp id 1 not exist", //查询失败的信息
"data": {}
}
实现代码如下:
func HandleDeleteExpression(ctx context.Context, c *app.RequestContext) {
id, err := strconv.Atoi(c.Param("id"))
if err != nil {
BindResp(c, ParamErrCode, err.Error(), nil)
return
}
exps, _ := dal.GetExpressionByID(uint(id))
if len(exps.Exp) == 0 {
msg := fmt.Sprintf("exp id %d not exist", id)
BindResp(c, DatabaseErrCode, msg, nil)
return
}
err = dal.DeleteExpressionByID(uint(id))
if err != nil {
BindResp(c, DatabaseErrCode, "failed delete Exp from database", nil)
return
}
BindResp(c, SuccessCode, SuccessMsg, exps)
}
实现较为简单,首先查询数据库中是否存在id对应的数据,如果不存在,则返回删除错误信息,存在则将其id以及表达式作为返回体返回。