智能风控决策引擎系统代码实现篇(十一)基本信息定义【操作符、特征、规则】

前几篇文章我们已经介绍完了服务启动时的各种加载和启动工作,但实际最底层工作的最小单元是规则,规则的执行则依赖特征操作符,本文我们就介绍与它们相关的代码。

一、操作符

因为操作符属于不会变的信息,所以可以放到常量相关的包中

risk_engine/configs/const.go

该文件中定义了以下信息:

  1. 规则支持的所有操作符,以及不同特征类型支持的操作符。
  2. 特征支持的数据类型
  3. 节点类型
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
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值