前几篇文章我们已经介绍完了服务启动时的各种加载和启动工作,但实际最底层工作的最小单元是规则
,规则的执行则依赖特征
和操作符
,本文我们就介绍与它们相关的代码。
一、操作符
因为操作符属于不会变的信息,所以可以放到常量相关的包中
risk_engine/configs/const.go
该文件中定义了以下信息:
- 规则支持的所有操作符,以及不同特征类型支持的操作符。
- 特征支持的数据类型
- 节点类型
package configs
const (
GT = "GT"
LT = "LT"
GE = "GE"
LE = "LE"
EQ = "EQ"
NEQ = "NEQ"
BETWEEN = "BETWEEN"
LIKE = "LIKE"
IN = "IN"
CONTAIN = "CONTAIN"
BEFORE = "BEFORE"
AFTER = "AFTER"
KEYEXIST = "KEYEXIST"
VALUEEXIST = "VALUEEXIST"
AND = "AND"
OR = "OR"
)
// OperatorMap 操作符转换为规则执行可识别的符号(goevaluate)
var OperatorMap = map[string]string{
GT: ">",
LT: "<",
GE: ">=",
LE: "<=",
EQ: "==",
NEQ: "!=",
BETWEEN: "between",
LIKE: "like",
IN: "in",
CONTAIN: "contain",
BEFORE: "before",
AFTER: "after",
KEYEXIST: "keyexist",
VALUEEXIST: "valueexist",
AND: "&&",
OR: "||",
}
// NumSupportOperator 数字型特征支持的操作
var NumSupportOperator = map[string]struct{}{
GT: struct{}{},
LT: struct{}{},
GE: struct{}{},
LE: struct{}{},
EQ: struct{}{},
NEQ: struct{}{},
BETWEEN: struct{}{},
IN: struct{}{},
}
// StringSupportOperator 字符串型特征支持的操作
var StringSupportOperator = map[string]struct{}{
EQ: struct{}{},
NEQ: struct{}{},
LIKE: struct{}{},
IN: struct{}{},
}
// EnumSupportOperator 枚举型特征支持的操作
var EnumSupportOperator = map[string]struct{}{
EQ: struct{}{},
NEQ: struct{}{},
}
// BoolSupportOperator 布尔型特征支持的操作
var BoolSupportOperator = map[string]struct{}{
EQ: struct{}{},
NEQ: struct{}{},
}
// DateSupportOperator 日期型特征支持的操作
var DateSupportOperator = map[string]struct{}{
BEFORE: struct{}{},
AFTER: struct{}{},
EQ: struct{}{},
NEQ: struct{}{},
BETWEEN: struct{}{},
}
// ArraySupportOperator 数组型特征支持的操作
var ArraySupportOperator = map[string]struct{}{
EQ: struct{}{},
NEQ: struct{}{},
CONTAIN: struct{}{},
IN: struct{}{},
}
// MapSupportOperator 数组型特征支持的操作
var MapSupportOperator = map[string]struct{}{
KEYEXIST: struct{}{},
VALUEEXIST: struct{}{},
}
// DefaultSupportOperator 默认支持的操作
var DefaultSupportOperator = map[string]struct{}{
EQ: struct{}{},
NEQ: struct{}{},
}
// CompareOperators 比较操作符
var CompareOperators = map[string]struct{}{
EQ: struct{}{},
NEQ: struct{}{},
GT: struct{}{},
LT: struct{}{},
GE: struct{}{},
LE: struct{}{},
}
// BooleanOperators 逻辑操作符
var BooleanOperators = map[string]string{
AND: OperatorMap[AND],
OR: OperatorMap[OR],
}
// 所有的节点类型
const (
START = "start"
END = "end"
RULESET = "ruleset"
ABTEST = "abtest"
CONDITIONAL = "conditional"
DECISIONTREE = "tree"
DECISIONMATRIX = "matrix"
SCORECARD = "scorecard"
)
// matrix
const (
MATRIXX = "matrixX"
MATRIXY = "matrixY"
)
// 特征支持的所有类型
const (
INT = "int"
FLOAT = "float"
STRING = "string"
BOOL = "bool"
DATE = "date"
ARRAY = "array"
MAP = "map"
DEFAULT = "default"
)
// 日期格式
const (
DATE_FORMAT = "2006-01-02"
DATE_FORMAT_DETAIL = "2006-01-02 15:04:05"
)
比较操作
risk_engine/operator/compare.go
package operator
import (
"github.com/liyouming/risk_engine/configs"
"github.com/liyouming/risk_engine/internal/errcode"
"github.com/liyouming/risk_engine/internal/log"
"github.com/liyouming/risk_engine/internal/util"
)
// compare operator expression
// left [operator] right
// operator: [eq,neq,gt,lt,ge,le]
func Compare(operator string, left interface{}, right interface{}) (bool, error) {
log.Infof("compare operator: %v", operator, left, right)
if _, ok := configs.CompareOperators[operator]; !ok {
log.Errorf("not compare operator: %v", operator)
return false, errcode.ErrorNotSupportOperator
}
switch operator {
case configs.EQ:
return equal(left, right)
case configs.NEQ:
rs, err := equal(left, right)
return !rs, err
//only number can compare(gt,lt,ge,le)
case configs.GT:
return numCompare(left, right, operator)
case configs.LT:
return numCompare(left, right, operator)
case configs.GE:
return numCompare(left, right, operator)
case configs.LE:
return numCompare(left, right, operator)
}
return false, errcode.ErrorNotSupportOperator
}
// jundge left == right
func equal(left, right interface{}) (bool, error) {
leftType, err := util.GetType(left)
if err != nil {
log.Errorf("left type unknow: %v", left, err)
return false, err //unknow type
}
rightType, err := util.GetType(right)
if err != nil {
log.Errorf("right type unknow: %v", right, err)
return false, err
}
if !util.MatchType(leftType, rightType) {
return false, nil
}
if leftType == configs.ARRAY {
return arrayEqual(left.([]interface{}), right.([]interface{})), nil
}
if leftType == configs.MAP {
return false, errcode.ErrorNotSupportOperator
}
if leftType == configs.STRING {
return left.(string) == right.(string), nil
}
if leftType == configs.BOOL {
leftBool, err := util.ToBool(left)
if err != nil {
return false, err
}
rightBool, err := util.ToBool(right)
if err != nil {
return false, err
}
return leftBool == rightBool, nil
}
if leftType == configs.INT || leftType == configs.FLOAT {
leftNum, err := util.ToFloat64(left)
if err != nil {
return false, err
}
rightNum, err := util.ToFloat64(right)
if err != nil {
return false, err
}
return numCompare(leftNum, rightNum, configs.EQ)
}
if leftType == configs.DATE {
leftDate, err := util.ToDate(left)
if err != nil {
return false, err
}
rightDate, err := util.ToDate(right)
if err != nil {
return false, err
}
return leftDate.Equal(rightDate), nil
}
if leftType == configs.DEFAULT {
return left == right, nil
}
return false, errcode.ErrorNotSupportOperator
}
// a == b true
// a != b false
func arrayEqual(a, b []interface{}) bool {
if len(a) != len(b) {
return false
}
if (a == nil) != (b == nil) {
return false
}
b = b[:len(a)]
tmp := make(map[interface{}]struct{}, len(b))
for _, v := range b {
tmp[v] = struct{}{}
}
for _, v := range a {
if _, ok := tmp[v]; !ok {
return false
}
}
return true
}
// compare number (lt, gt, le, ge, eq, neq)
// only number can compare
func numCompare(left, right interface{}, op string) (bool, error) {
leftNum, err := util.ToFloat64(left)
if err != nil {
return false, errcode.ErrorNotANumber
}
rightNum, err := util.ToFloat64(right)
if err != nil {
return false, errcode.ErrorNotANumber
}
switch op {
case configs.EQ:
return leftNum == rightNum, nil
case configs.NEQ:
return leftNum != rightNum, nil
case configs.GT:
return leftNum > rightNum, nil
case configs.LT:
return leftNum < rightNum, nil
case configs.GE:
return leftNum >= rightNum, nil
case configs.LE:
return leftNum <= rightNum, nil
default:
return false, errcode.ErrorNotSupportOperator
}
}
表达式执行
risk_engine/operator/eval.go
package operator
import (
"errors"
"github.com/Knetic/govaluate"
"github.com/liyouming/risk_engine/internal/log"
)
// using govalute to execute expression
func Evaluate(exprStr string, params map[string]interface{}) (bool, error) {
expr, err := govaluate.NewEvaluableExpression(exprStr)
log.Infof("base evaluate: %v", expr, params)
if err != nil {
return false, err
}
eval, err := expr.Evaluate(params)
if err != nil {
return false, err
}
if result, ok := eval.(bool); ok {
return result, nil
}
return false, errors.New("convert error")
}
逻辑操作
risk_engine/operator/logic.go
package operator
import (
"fmt"
"strings"
)
// evaluate 计算逻辑表达式的值
func EvaluateBoolExpr(expr string, variables map[string]bool) (bool, error) {
// 将表达式拆分成一个个token
tokens, err := splitExpression(expr)
if err != nil {
return false, err
}
// 开始执行逻辑运算
stack := make([]bool, 0)
opStack := make([]string, 0)
for _, token := range tokens {
switch token {
case "&&":
for len(opStack) > 0 && opStack[len(opStack)-1] == "!" {
if len(stack) < 1 {
return false, fmt.Errorf("invalid expression")
}
b := stack[len(stack)-1]
stack = stack[:len(stack)-1]
stack = append(stack, !b)
opStack = opStack[:len(opStack)-1]
}
opStack = append(opStack, "&&")
case "||":
for len(opStack) > 0 && (opStack[len(opStack)-1] == "!" || opStack[len(opStack)-1] == "&&") {
if len(stack) < 2 {
return false, fmt.Errorf("invalid expression")
}
b1, b2 := stack[len(stack)-2], stack[len(stack)-1]
stack = stack[:len(stack)-2]
op := opStack[len(opStack)-1]
opStack = opStack[:len(opStack)-1]
stack = append(stack, evaluateOp(b1, b2, op))
}
opStack = append(opStack, "||")
case "!":
opStack = append(opStack, "!")
case "(":
opStack = append(opStack, "(")
case ")":
if len(opStack) < 1 {
return false, fmt.Errorf("invalid expression")
}
for opStack[len(opStack)-1] != "(" {
if len(stack) < 2 {
return false, fmt.Errorf("invalid expression")
}
b1, b2 := stack[len(stack)-2], stack[len(stack)-1]
stack = stack[:len(stack)-2]
op := opStack[len(opStack)-1]
opStack = opStack[:len(opStack)-1]
stack = append(stack, evaluateOp(b1, b2, op))
if len(opStack) == 0 {
return false, fmt.Errorf("unmatched parentheses")
}
}
opStack = opStack[:len(opStack)-1]
if len(opStack) > 0 && opStack[len(opStack)-1] == "!" {
if len(stack) < 1 {
return false, fmt.Errorf("invalid expression")
}
b := stack[len(stack)-1]
stack = stack[:len(stack)-1]
stack = append(stack, !b)
opStack = opStack[:len(opStack)-1]
}
default:
if v, ok := variables[token]; ok {
stack = append(stack, v)
if len(opStack) > 0 && opStack[len(opStack)-1] == "!" {
if len(stack) < 1 {
return false, fmt.Errorf("invalid expression")
}
b := stack[len(stack)-1]
stack = stack[:len(stack)-1]
stack = append(stack, !b)
opStack = opStack[:len(opStack)-1]
}
} else {
return false, fmt.Errorf("unknown variable %s", token)
}
}
}
for len(opStack) > 0 {
if len(stack) < 2 {
return false, fmt.Errorf("invalid expression")
}
b1, b2 := stack[len(stack)-2], stack[len(stack)-1]
stack = stack[:len(stack)-2]
op := opStack[len(opStack)-1]
opStack = opStack[:len(opStack)-1]
stack = append(stack, evaluateOp(b1, b2, op))
}
if len(stack) != 1 {
return false, fmt.Errorf("invalid expression")
}
return stack[0], nil
}
// evaluateOp 对两个 bool 值进行逻辑运算
func evaluateOp(b1, b2 bool, op string) bool {
switch op {
case "&&":
return b1 && b2
case "||":
return b1 || b2
default:
panic("unsupported operator: " + op)
}
}
// isValid 检查表达式是否合法
func isValid(expr string) bool {
if len(expr) == 0 {
return false
}
allowed := "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!()+-_*%/|&,"
stack := make([]rune, 0)
for _, ch := range expr {
if ch == '(' {
stack = append(stack, ch)
} else if ch == ')' {
if len(stack) == 0 {
return false
}
stack = stack[:len(stack)-1]
} else if !strings.ContainsRune(allowed, ch) {
return false
}
}
return len(stack) == 0
}
// splitExpression 将表达式拆分为token
func splitExpression(expr string) ([]string, error) {
expr = strings.ReplaceAll(expr, " ", "") // 去除空格
if !isValid(expr) {
return nil, fmt.Errorf("invalid expression")
}
tokens := make([]string, 0)
buf := make([]rune, 0)
for i := 0; i < len(expr); i++ {
ch := rune(expr[i])
if ch == '&' && i < len(expr)-1 && rune(expr[i+1]) == '&' {
if len(buf) > 0 {
tokens = append(tokens, string(buf))
buf = []rune{}
}
tokens = append(tokens, "&&")
i++
} else if ch == '|' && i < len(expr)-1 && rune(expr[i+1]) == '|' {
if len(buf) > 0 {
tokens = append(tokens, string(buf))
buf = []rune{}
}
tokens = append(tokens, "||")
i++
} else if ch == '!' || ch == '(' || ch == ')' {
if len(buf) > 0 {
tokens = append(tokens, string(buf))
buf = []rune{}
}
tokens = append(tokens, string(ch))
} else if ch == ',' {
if len(buf) > 0 {
tokens = append(tokens, string(buf))
buf = []rune{}
}
tokens = append(tokens, string(ch))
} else {
buf = append(buf, ch)
}
}
if len(buf) > 0 {
tokens = append(tokens, string(buf))
}
return tokens, nil
}
二、特征
risk_engine/core/feature
package core
import (
"github.com/liyouming/risk_engine/configs"
"github.com/liyouming/risk_engine/internal/errcode"
"github.com/liyouming/risk_engine/internal/log"
"github.com/liyouming/risk_engine/internal/operator"
"github.com/liyouming/risk_engine/internal/util"
"strings"
"time"
)
type FeatureType int
const (
TypeInt FeatureType = iota
TypeFloat
TypeString
TypeBool
TypeDate
TypeArray
TypeMap
TypeDefault
)
var FeatureTypeMap = map[string]FeatureType{
configs.INT: TypeInt,
configs.FLOAT: TypeFloat,
configs.STRING: TypeString,
configs.BOOL: TypeBool,
configs.DATE: TypeDate,
configs.ARRAY: TypeArray,
configs.MAP: TypeMap,
configs.DEFAULT: TypeDefault,
}
var FeatureStrMap = map[FeatureType]string{
TypeInt: configs.INT,
TypeFloat: configs.FLOAT,
TypeString: configs.STRING,
TypeBool: configs.BOOL,
TypeDate: configs.DATE,
TypeArray: configs.ARRAY,
TypeMap: configs.MAP,
TypeDefault: configs.DEFAULT,
}
func GetFeatureType(name string) FeatureType {
return FeatureTypeMap[name]
}
func (featureType FeatureType) String() string {
return FeatureStrMap[featureType]
}
// Feature 对应dsl中的特征结构,用于存元信息,而决策流依赖的特征是IFeature接口,可具体执行的
type Feature struct {
Id int `yaml:"id"`
Name string `yaml:"name"`
Tag string `yaml:"tag"`
Label string `yaml:"label"`
Kind string `yaml:"kind"`
}
type IFeature interface {
GetName() string
SetValue(value interface{}) error
GetValue() (interface{}, bool)
GetType() FeatureType
SupportOperators() map[string]struct{}
Compare(op string, value interface{}) (bool, error)
}
func NewFeature(name string, kind FeatureType) (feature IFeature) {
switch kind {
case TypeInt:
fallthrough
case TypeFloat:
feature = &TypeNumFeature{
Name: name,
Kind: kind,
}
case TypeString:
feature = &TypeStringFeature{
Name: name,
Kind: kind,
}
case TypeBool:
feature = &TypeBoolFeature{
Name: name,
Kind: kind,
}
case TypeDate:
feature = &TypeDateFeature{
Name: name,
Kind: kind,
}
case TypeArray:
feature = &TypeArrayFeature{
Name: name,
Kind: kind,
}
case TypeMap:
feature = &TypeMapFeature{
Name: name,
Kind: kind,
}
default:
feature = &TypeDefaultFeature{
Name: name,
Kind: kind,
}
}
return
}
// 数值类型
type TypeNumFeature struct {
Name string
Kind FeatureType
Value interface{}
DefaultValue interface{}
}
func (feature *TypeNumFeature) GetType() FeatureType {
return feature.Kind
}
func (feature *TypeNumFeature) SupportOperators() map[string]struct{} {
return configs.NumSupportOperator
}
func (feature *TypeNumFeature) SetValue(value interface{}) error {
if err := checkValue(value, feature.GetType()); err != nil {
return err
}
feature.Value = value
return nil
}
func (feature *TypeNumFeature) GetValue() (interface{}, bool) {
if feature.Value == nil { //取不到走默认值
return feature.DefaultValue, false
}
return feature.Value, true
}
func (feature *TypeNumFeature) GetName() string {
return feature.Name
}
func (feature *TypeNumFeature) Compare(op string, target interface{}) (bool, error) {
if _, ok := feature.SupportOperators()[op]; !ok {
return false, errcode.ParseErrorNotSupportOperator
}
value, _ := feature.GetValue() //默认值处理
switch op {
case "GT":
fallthrough
case "LT":
fallthrough
case "GE":
fallthrough
case "LE":
fallthrough
case configs.EQ:
fallthrough
case "NEQ":
rs, err := operator.Compare(op, value, target)
return rs, err
case "BETWEEN": //todo: [ ( ) ]
if t, ok := target.([]interface{}); !ok {
return false, errcode.ParseErrorTargetMustBeArray
} else {
if len(t) != 2 {
return false, errcode.ParseErrorTargetMustBeArray
}
left, _ := util.ToFloat64(t[0])
right, _ := util.ToFloat64(t[1])
if left > right {
left, right = right, left
}
rs1, err := operator.Compare("GT", value, left)
if err != nil {
return false, err
}
rs2, err := operator.Compare("LT", value, right)
if err != nil {
return false, err
}
return rs1 && rs2, nil
}
case "IN":
if t, ok := target.([]interface{}); !ok {
return false, errcode.ParseErrorTargetMustBeArray
} else {
return operator.InArray(t, value), nil
}
default:
return false, errcode.ParseErrorNotSupportOperator
}
return false, errcode.ParseErrorNotSupportOperator
}
// 字符串类型
type TypeStringFeature struct {
Name string
Kind FeatureType
Value interface{}
DefaultValue interface{}
}
func (feature *TypeStringFeature) GetType() FeatureType {
return feature.Kind
}
func (feature *TypeStringFeature) SupportOperators() map[string]struct{} {
return configs.StringSupportOperator
}
func (feature *TypeStringFeature) SetValue(value interface{}) error {
if err := checkValue(value, feature.GetType()); err != nil {
return err
}
feature.Value = value
return nil
}
func (feature *TypeStringFeature) GetValue() (interface{}, bool) {
if feature.Value == nil { //取不到走默认值
return feature.DefaultValue, false
}
return feature.Value, true
}
func (feature *TypeStringFeature) GetName() string {
return feature.Name
}
func (feature *TypeStringFeature) Compare(op string, target interface{}) (bool, error) {
if _, ok := feature.SupportOperators()[op]; !ok {
return false, errcode.ParseErrorNotSupportOperator
}
value, _ := feature.GetValue() //默认值处理
switch op {
case configs.EQ:
fallthrough
case "NEQ":
rs, err := operator.Compare(op, value, target)
return rs, err
case "LIKE":
if ok := strings.Contains(value.(string), target.(string)); ok {
return true, nil
} else {
return false, nil
}
case "IN":
if t, ok := target.([]interface{}); !ok {
return false, errcode.ParseErrorTargetMustBeArray
} else {
return operator.InArray(t, value), nil
}
default:
return false, errcode.ParseErrorNotSupportOperator
}
return false, errcode.ParseErrorNotSupportOperator
}
// bool类型
type TypeBoolFeature struct {
Name string
Kind FeatureType
Value interface{}
DefaultValue interface{}
}
func (feature *TypeBoolFeature) GetType() FeatureType {
return feature.Kind
}
func (feature *TypeBoolFeature) SupportOperators() map[string]struct{} {
return configs.BoolSupportOperator
}
func (feature *TypeBoolFeature) SetValue(value interface{}) error {
if err := checkValue(value, feature.GetType()); err != nil {
return err
}
feature.Value = value
return nil
}
func (feature *TypeBoolFeature) GetValue() (interface{}, bool) {
return feature.Value, true
}
func (feature *TypeBoolFeature) GetName() string {
return feature.Name
}
func (feature *TypeBoolFeature) Compare(op string, target interface{}) (bool, error) {
if _, ok := feature.SupportOperators()[op]; !ok {
return false, errcode.ParseErrorNotSupportOperator
}
value, _ := feature.GetValue()
return operator.Compare(op, value, target)
}
// date类型
type TypeDateFeature struct {
Name string
Kind FeatureType
Value interface{}
DefaultValue interface{}
}
func (feature *TypeDateFeature) GetType() FeatureType {
return feature.Kind
}
func (feature *TypeDateFeature) SupportOperators() map[string]struct{} {
return configs.DateSupportOperator
}
func (feature *TypeDateFeature) SetValue(value interface{}) error {
if err := checkValue(value, feature.GetType()); err != nil {
return err
}
feature.Value = value
return nil
}
func (feature *TypeDateFeature) GetValue() (interface{}, bool) {
return feature.Value, true
}
func (feature *TypeDateFeature) GetName() string {
return feature.Name
}
func (feature *TypeDateFeature) Compare(op string, target interface{}) (bool, error) {
if _, ok := feature.SupportOperators()[op]; !ok {
return false, errcode.ParseErrorNotSupportOperator
}
value, _ := feature.GetValue()
valueTime, ok := value.(time.Time)
if !ok {
return false, errcode.ParseErrorFeatureTypeNotMatch
}
var (
targetTime time.Time
targetTimeLeft time.Time
targetTimeRight time.Time
isTargetArr bool
)
var err error
switch target.(type) {
case string:
targetTime, err = util.ToDate(target.(string))
if err != nil {
return false, err
}
case time.Time:
targetTime = target.(time.Time)
case []string:
if targetArr := target.([]string); len(targetArr) != 2 {
return false, errcode.ParseErrorTargetNotSupport
} else {
targetTimeLeft, err = util.ToDate(targetArr[0])
if err != nil {
return false, err
}
targetTimeRight, err = util.ToDate(targetArr[1])
if err != nil {
return false, err
}
isTargetArr = true
}
case []time.Time:
if targetArr := target.([]time.Time); len(targetArr) != 2 {
return false, errcode.ParseErrorTargetNotSupport
} else {
targetTimeLeft = targetArr[0]
targetTimeRight = targetArr[1]
isTargetArr = true
}
default:
return false, errcode.ParseErrorTargetNotSupport
}
if isTargetArr && op != "BETWEEN" || !isTargetArr && op == "BETWEEN" {
return false, errcode.ParseErrorTargetNotSupport
}
switch op {
case "BEFORE":
return valueTime.Before(targetTime), nil
case "AFTER":
return valueTime.After(targetTime), nil
case configs.EQ:
return valueTime.Equal(targetTime), nil
case "NEQ":
return !valueTime.Equal(targetTime), nil
case "BETWEEN":
return valueTime.After(targetTimeLeft) && valueTime.Before(targetTimeRight), nil
}
return false, errcode.ParseErrorNotSupportOperator
}
// Array类型
type TypeArrayFeature struct {
Name string
Kind FeatureType
Value interface{}
DefaultValue interface{}
}
func (feature *TypeArrayFeature) GetType() FeatureType {
return feature.Kind
}
func (feature *TypeArrayFeature) SupportOperators() map[string]struct{} {
return configs.ArraySupportOperator
}
func (feature *TypeArrayFeature) SetValue(value interface{}) error {
if err := checkValue(value, feature.GetType()); err != nil {
return err
}
valueArr, ok := value.([]interface{})
if !ok {
return errcode.ParseErrorFeatureSetValue
}
for k, v := range valueArr {
if util.IsInt(v) {
valueArr[k], _ = util.ToInt(v)
}
}
feature.Value = valueArr
return nil
}
func (feature *TypeArrayFeature) GetValue() (interface{}, bool) {
if feature.Value == nil { //取不到走默认值
return feature.DefaultValue, false
}
return feature.Value, true
}
func (feature *TypeArrayFeature) GetName() string {
return feature.Name
}
func (feature *TypeArrayFeature) Compare(op string, target interface{}) (bool, error) {
if _, ok := feature.SupportOperators()[op]; !ok {
return false, errcode.ParseErrorNotSupportOperator
}
value, _ := feature.GetValue()
valueArr, ok := value.([]interface{})
if !ok {
return false, errcode.ParseErrorFeatureTypeNotMatch
}
targetArr, isTargetArray := target.([]interface{})
if !isTargetArray && op != configs.CONTAIN { //contain target can be simple data
return false, errcode.ParseErrorTargetMustBeArray
}
switch op {
case configs.EQ:
return operator.Compare(op, valueArr, targetArr)
case configs.NEQ:
return operator.Compare(op, valueArr, targetArr)
case configs.IN:
return operator.AInB(valueArr, targetArr), nil
case configs.CONTAIN:
if isTargetArray {
return operator.AInB(targetArr, valueArr), nil
}
return operator.InArray(valueArr, target), nil
}
return false, errcode.ParseErrorNotSupportOperator
}
// map类型
type TypeMapFeature struct {
Name string
Kind FeatureType
Value interface{}
DefaultValue interface{}
}
func (feature *TypeMapFeature) GetType() FeatureType {
return feature.Kind
}
func (feature *TypeMapFeature) SupportOperators() map[string]struct{} {
return configs.MapSupportOperator
}
func (feature *TypeMapFeature) SetValue(value interface{}) error {
if err := checkValue(value, feature.GetType()); err != nil {
return err
}
feature.Value = value
return nil
}
func (feature *TypeMapFeature) GetValue() (interface{}, bool) {
if feature.Value == nil { //取不到走默认值
return feature.DefaultValue, false
}
return feature.Value, true
}
func (feature *TypeMapFeature) GetName() string {
return feature.Name
}
func (feature *TypeMapFeature) Compare(op string, target interface{}) (bool, error) {
if _, ok := feature.SupportOperators()[op]; !ok {
return false, errcode.ParseErrorNotSupportOperator
}
value, _ := feature.GetValue() //默认值处理
valueMap, ok := value.(map[string]interface{})
if !ok {
return false, errcode.ParseErrorFeatureTypeNotMatch
}
switch op {
case configs.KEYEXIST:
targetStr, err := util.ToString(target)
if err != nil {
return false, err
}
if _, ok := valueMap[targetStr]; ok {
return true, nil
}
return false, nil
case configs.VALUEEXIST:
for _, v := range valueMap {
if v == target {
return true, nil
}
}
return false, nil
}
return false, errcode.ParseErrorNotSupportOperator
}
// 默认类型
type TypeDefaultFeature struct {
Name string
Kind FeatureType
Value interface{}
DefaultValue interface{}
}
func (feature *TypeDefaultFeature) GetType() FeatureType {
return feature.Kind
}
func (feature *TypeDefaultFeature) SupportOperators() map[string]struct{} {
return configs.DefaultSupportOperator
}
func (feature *TypeDefaultFeature) SetValue(value interface{}) error {
feature.Value = value
return nil
}
func (feature *TypeDefaultFeature) GetValue() (interface{}, bool) {
if feature.Value == nil { //取不到走默认值
return feature.DefaultValue, false
}
return feature.Value, true
}
func (feature *TypeDefaultFeature) GetName() string {
return feature.Name
}
func (feature *TypeDefaultFeature) Compare(op string, target interface{}) (bool, error) {
if _, ok := feature.SupportOperators()[op]; !ok {
return false, errcode.ParseErrorNotSupportOperator
}
value, _ := feature.GetValue() //默认值处理
return operator.Compare(op, value, target)
}
func checkValue(value interface{}, featureType FeatureType) error {
valueType, err := util.GetType(value)
if err != nil {
log.Warnf("set value error! get value type error! %s", err)
return errcode.ParseErrorFeatureSetValue
}
if !util.MatchType(valueType, featureType.String()) {
log.Warnf("set value not match %s", valueType, featureType.String())
return errcode.ParseErrorFeatureSetValue
}
return nil
}
三、规则
risk_engine/core/common.go
package core
import (
"errors"
"fmt"
"github.com/liyouming/risk_engine/internal/errcode"
"github.com/liyouming/risk_engine/internal/log"
"github.com/liyouming/risk_engine/internal/operator"
)
type NodeInfo struct {
Id int64 `yaml:"id"`
Name string `yaml:"name"`
Tag string `yaml:"tag"`
Label string `yaml:"label"`
Kind string `yaml:"kind"`
Depends []string `yaml:"depends,flow"`
}
type BlockStrategy struct {
IsBlock bool `yaml:"is_block"`
HitRule []string `yaml:"hit_rule,flow"`
Operator string `yaml:"operator"`
Value interface{} `yaml:"value"`
}
type Rule struct {
Id string `yaml:"id"`
Name string `yaml:"name"`
Tag string `yaml:"tag"`
Label string `yaml:"label"`
Kind string `yaml:"kind"`
Conditions []Condition `yaml:"conditions,flow"`
Decision Decision `yaml:"decision"`
}
//parse rule
func (rule *Rule) Parse(ctx *PipelineContext, depends map[string]IFeature) (output *Output, err error) {
output = &rule.Decision.Output
//rule.Conditions
if len(rule.Conditions) == 0 {
err = errors.New(fmt.Sprintf("rule (%s) condition is empty", rule.Name))
log.Error(err)
return
}
var conditionRet = make(map[string]bool, 0)
for _, condition := range rule.Conditions {
if feature, ok := depends[condition.Feature]; ok {
rs, err := feature.Compare(condition.Operator, condition.Value)
if err != nil {
log.Error(err)
return output, nil //value deafult
}
conditionRet[condition.Name] = rs
} else {
//lack of feature whether ignore
log.Error("error lack of feature: %s", condition.Feature)
//continue
}
}
if len(conditionRet) == 0 {
err = errors.New(fmt.Sprintf("rule (%s) condition result error", rule.Name))
return
}
//rule.Decision
expr := rule.Decision.Logic
logicRet, err := operator.EvaluateBoolExpr(expr, conditionRet)
//某个表达式执行失败会导致最终逻辑执行失败
if err != nil {
return
}
log.Infof("rule result: %v", rule.Label, rule.Name, logicRet)
output.SetHit(logicRet)
//assign
if len(rule.Decision.Assign) > 0 && logicRet {
features := make(map[string]IFeature)
for name, value := range rule.Decision.Assign {
feature := NewFeature(name, TypeDefault) //string
feature.SetValue(value)
features[name] = feature
}
ctx.SetFeatures(features)
}
return output, nil
}
type Condition struct {
Feature string `yaml:"feature"`
Operator string `yaml:"operator"`
Value interface{} `yaml:"value"`
Goto string `yaml:"goto"`
Result string `yaml:"result"`
Name string `yaml:"name"`
}
type Decision struct {
Depends []string `yaml:"depends,flow"` //依赖condition结果
Logic string `yaml:"logic"`
Output Output `yaml:"output"`
Assign map[string]interface{} `yaml:"assign"` //赋值更多变量
}
type Output struct {
Name string `yaml:"name"` //该节点输出值重命名,如果无则以(节点类型+节点名)赋值变量
Value interface{} `yaml:"value"`
Kind string `yaml:"kind"` //nodetype featuretype
Hit bool
}
func (output *Output) SetHit(hit bool) {
output.Hit = hit
}
func (output *Output) GetHit() bool {
return output.Hit
}
type Branch struct {
Name string `yaml:"name"`
Conditions []Condition `yaml:"conditions"` //used by conditional
Percent float64 `yaml:"percent"` //used by abtest
Decision Decision `yaml:"decision"`
}
type Block struct {
Name string `yaml:"name"`
Feature string `yaml:"feature"`
Conditions []Condition `yaml:"conditions,flow"`
}
//return result, goto, error
func (block Block) parse(depends map[string]IFeature) (interface{}, bool, error) {
if block.Conditions == nil || len(block.Conditions) == 0 {
return nil, false, nil
}
for _, condition := range block.Conditions {
if feature, ok := depends[block.Feature]; ok {
if v, _ := feature.GetValue(); v == nil {
log.Errorf("feature %s empty", feature.GetName())
continue
}
hit, err := feature.Compare(condition.Operator, condition.Value)
if err != nil {
log.Errorf("parse error %s", err)
continue
}
if hit {
if condition.Goto != "" {
return condition.Goto, true, nil
} else {
return condition.Result, false, nil
}
}
} else {
log.Errorf("lack of feature: %s", block.Feature)
continue
}
}
return nil, false, errcode.ParseErrorBlockNotMatch
}