golang,go,博客,开源,编程

golang每日一库之bytedance/go-tagexpr

Updated on with 0 views and 0 comments

github.com/bytedance/go-tagexpr/v2 是字节跳动公司开源的一个高性能、灵活的 Go 语言库,主要用于解析和执行标签表达式(Tag Expression)。标签表达式通常用于在结构体、数据记录或其他上下文中动态地评估条件,例如在监控系统、数据过滤和规则引擎等场景中非常有用。

该库允许开发者通过结构体标签定义表达式,表达式可以基于某些条件来决定数据是否满足特定规则。它支持常见的条件运算符(如 =><= 等),并且可以扩展自定义函数和操作符,具有很强的灵活性。

核心特性:

  1. 高性能:优化的解析器和计算引擎,能够处理大规模数据。
  2. 灵活的表达式语法:支持逻辑运算符、比较运算符、函数调用等多种表达式形式。
  3. 可扩展:支持自定义函数和扩展运算符,能够灵活满足不同需求。
  4. 结构体标签解析:可以解析结构体的标签,允许在结构体的字段标签中嵌入表达式,动态计算字段值。

主要功能:

  • 通过结构体标签(tag)来定义表达式。
  • 支持基本的逻辑运算符(and, or, not)和比较运算符(=, !=, >, <, >= 等)。
  • 支持内置函数(如 len())和自定义函数。
  • 高效的表达式解析和评估,适用于大规模数据处理和动态配置。
  • 支持嵌套表达式和复杂的逻辑组合。

1. 基本用法

首先,我们从简单的标签表达式开始,展示如何解析和评估表达式。

示例 1:基本标签表达式解析

假设我们有一个标签表达式 "env=prod and method=GET",我们希望检查是否有一个标签集合符合这个表达式。

package main

import (
    "fmt"
    "github.com/bytedance/go-tagexpr/v2"
)

func main() {
    // 标签表达式
    exprStr := "env=prod and method=GET"

    // 解析表达式
    expr, err := tagexpr.Parse(exprStr)
    if err != nil {
        fmt.Printf("Error parsing expression: %v\n", err)
        return
    }

    // 定义标签集合
    tags := map[string]string{
        "env":    "prod",
        "method": "GET",
        "status": "200",
    }

    // 评估表达式
    result, err := expr.Evaluate(tags)
    if err != nil {
        fmt.Printf("Error evaluating expression: %v\n", err)
        return
    }

    fmt.Printf("Expression result: %v\n", result) // true
}

解析:

  • exprStr := "env=prod and method=GET":这是我们定义的标签表达式,要求标签中同时满足 env=prodmethod=GET
  • tags := map[string]string{...}:这是标签集合,用于评估表达式。
  • expr.Evaluate(tags):评估表达式是否满足给定的标签集合。

输出:

Expression result: true


2. 复杂表达式

示例 2:使用逻辑运算符和括号

你可以通过逻辑运算符(如 and, or, not)和括号来构造更复杂的表达式。例如,下面的表达式要求 envprodstaging,并且 method 不能是 POST

package main

import (
    "fmt"
    "github.com/bytedance/go-tagexpr/v2"
)

func main() {
    // 复杂表达式
    exprStr := "(env=prod or env=staging) and not method=POST"

    // 解析表达式
    expr, err := tagexpr.Parse(exprStr)
    if err != nil {
        fmt.Printf("Error parsing expression: %v\n", err)
        return
    }

    // 定义标签集合
    tags := map[string]string{
        "env":    "prod",
        "method": "GET",
    }

    // 评估表达式
    result, err := expr.Evaluate(tags)
    if err != nil {
        fmt.Printf("Error evaluating expression: %v\n", err)
        return
    }

    fmt.Printf("Expression result: %v\n", result) // true
}

解析:

  • (env=prod or env=staging):表示 env 可以是 prodstaging
  • not method=POST:表示 method 不能是 POST

输出:

Expression result: true

3. 比较运算符

go-tagexpr 支持常见的比较运算符,如 =, !=, >, <, >=, <=。我们来看看如何使用这些运算符。

示例 3:使用比较运算符

package main

import (
    "fmt"
    "github.com/bytedance/go-tagexpr/v2"
)

func main() {
    // 比较运算符
    exprStr := "age>=18 and status=active"

    // 解析表达式
    expr, err := tagexpr.Parse(exprStr)
    if err != nil {
        fmt.Printf("Error parsing expression: %v\n", err)
        return
    }

    // 定义标签集合
    tags := map[string]string{
        "age":    "20",    // 作为字符串处理
        "status": "active",
    }

    // 评估表达式
    result, err := expr.Evaluate(tags)
    if err != nil {
        fmt.Printf("Error evaluating expression: %v\n", err)
        return
    }

    fmt.Printf("Expression result: %v\n", result) // true
}

解析:

  • age>=18:检查 age 是否大于等于 18。
  • status=active:检查 status 是否为 active

输出:

Expression result: true

4. 使用内置函数

go-tagexpr 提供了一些内置函数,如 len() 用来计算字符串的长度。你还可以自定义函数并在表达式中使用。

示例 4:使用内置 len() 函数

package main

import (
    "fmt"
    "github.com/bytedance/go-tagexpr/v2"
)

func main() {
    // 使用 len() 函数
    exprStr := "len(name) > 3"

    // 解析表达式
    expr, err := tagexpr.Parse(exprStr)
    if err != nil {
        fmt.Printf("Error parsing expression: %v\n", err)
        return
    }

    // 定义标签集合
    tags := map[string]string{
        "name": "John",
    }

    // 评估表达式
    result, err := expr.Evaluate(tags)
    if err != nil {
        fmt.Printf("Error evaluating expression: %v\n", err)
        return
    }

    fmt.Printf("Expression result: %v\n", result) // true
}

解析:

  • len(name) > 3:检查标签 name 的值长度是否大于 3。

输出:

Expression result: true

5. 自定义函数

除了内置函数,go-tagexpr 还支持自定义函数,你可以创建自己的函数并将其注册到库中。

示例 5:注册自定义函数

假设我们需要一个自定义函数,判断字符串的首字母是否为大写:

package main

import (
    "fmt"
    "github.com/bytedance/go-tagexpr/v2"
    "strings"
)

// 自定义函数:判断首字母是否大写
func isUpperCase(args ...interface{}) (interface{}, error) {
    if len(args) != 1 {
        return nil, fmt.Errorf("isUpperCase requires exactly one argument")
    }
    str, ok := args[0].(string)
    if !ok {
        return nil, fmt.Errorf("isUpperCase argument must be a string")
    }
    return strings.ToUpper(string(str[0])) == string(str[0]), nil
}

func main() {
    // 注册自定义函数
    tagexpr.RegisterFunction("isUpperCase", isUpperCase)

    // 使用自定义函数的表达式
    exprStr := "isUpperCase(name)"

    // 解析表达式
    expr, err := tagexpr.Parse(exprStr)
    if err != nil {
        fmt.Printf("Error parsing expression: %v\n", err)
        return
    }

    // 定义标签集合
    tags := map[string]string{
        "name": "John",
    }

    // 评估表达式
    result, err := expr.Evaluate(tags)
    if err != nil {
        fmt.Printf("Error evaluating expression: %v\n", err)
        return
    }

    fmt.Printf("Expression result: %v\n", result) // true
}

解析:

  • isUpperCase(name):自定义函数判断标签 name 的首字母是否为大写。

输出:

Expression result: true

官方例子

package tagexpr_test

import (
	"fmt"

	tagexpr "github.com/bytedance/go-tagexpr/v2"
)

func Example() {
	type T struct {
		A  int             `tagexpr:"$<0||$>=100"`
		B  string          `tagexpr:"len($)>1 && regexp('^\\w*$')"`
		C  bool            `tagexpr:"expr1:(f.g)$>0 && $; expr2:'C must be true when T.f.g>0'"`
		d  []string        `tagexpr:"@:len($)>0 && $[0]=='D'; msg:sprintf('invalid d: %v',$)"`
		e  map[string]int  `tagexpr:"len($)==$['len']"`
		e2 map[string]*int `tagexpr:"len($)==$['len']"`
		f  struct {
			g int `tagexpr:"$"`
		}
		h  int 				`tagexpr:"$>minVal"`
	}

	vm := tagexpr.New("tagexpr")
	t := &T{
		A:  107,
		B:  "abc",
		C:  true,
		d:  []string{"x", "y"},
		e:  map[string]int{"len": 1},
		e2: map[string]*int{"len": new(int)},
		f: struct {
			g int `tagexpr:"$"`
		}{1},
		h: 10,
	}

	tagExpr, err := vm.Run(t)
	if err != nil {
		panic(err)
	}

	fmt.Println(tagExpr.Eval("A"))
	fmt.Println(tagExpr.Eval("B"))
	fmt.Println(tagExpr.Eval("C@expr1"))
	fmt.Println(tagExpr.Eval("C@expr2"))
	if !tagExpr.Eval("d").(bool) {
		fmt.Println(tagExpr.Eval("d@msg"))
	}
	fmt.Println(tagExpr.Eval("e"))
	fmt.Println(tagExpr.Eval("e2"))
	fmt.Println(tagExpr.Eval("f.g"))
	fmt.Println(tagExpr.EvalWithEnv("h", map[string]interface{}{"minVal": 9}))
	fmt.Println(tagExpr.EvalWithEnv("h", map[string]interface{}{"minVal": 11}))

	// Output:
	// true
	// true
	// true
	// C must be true when T.f.g>0
	// invalid d: [x y]
	// true
	// false
	// 1
	// true
	// false
}

6. 性能优化技巧

在高并发或大数据量场景下,go-tagexpr 提供了几个优化策略:

6.1 预编译表达式

如果你频繁评估相同的表达式,可以考虑先解析表达式,然后缓存解析结果,避免每次都重新解析。

expr, err := tagexpr.Parse("env=prod and method=GET")
if err != nil {
    fmt.Println("Error parsing expression:", err)
    return
}

// 之后多次评估
result, err := expr.Evaluate(tags)

6.2 减少标签数量

评估时,尽量减少传入的标签数量。避免传递多余的标签,提高效率。

tags := map[string]string{
    "env":    "prod",
    "method": "GET",
}

6.3 常见应用场景

  • 动态配置:使用标签表达式来动态计算配置项是否满足某些条件,尤其在分布式系统中非常有用。
  • 数据过滤:根据条件过滤数据记录,特别是在大规模数据处理中,可以通过标签表达式来选择合适的数据。
  • 规则引擎:通过标签表达式实现灵活的规则引擎,可以根据条件动态决定如何处理数据或操作。

总结

go-tagexpr/v2 是一个功能强大且高效的标签表达式解析库,能够帮助开发者通过标签定义和评估条件表达式。它支持丰富的表达式语法、内置和自定义函数,适用于大规模数据过滤、配置评估等应用场景。通过性能优化手段,它能在高频率和大数据量的场景下保持良好的性能表现。

你可以通过 GitHub 仓库访问更多的源代码、文档和示例:GitHub 仓库


标题:golang每日一库之bytedance/go-tagexpr
作者:mooncakeee
地址:http://blog.dd95828.com/articles/2025/01/15/1736902247154.html
联系:scotttu@163.com