package calculate import ( "errors" "math" "zhiyuan/pkg/db" "zhiyuan/pkg/logger" "zhiyuan/pkg/utils" "zhiyuan/services/admin" "github.com/Knetic/govaluate" "github.com/gin-gonic/gin" ) type Call struct { Name string `json:"name" label:"变量名"` Item *OrderItem `json:"item" label:"项目"` } type Stack struct { OrderNumber int64 `json:"order_number"` Item *OrderItem `json:"item" label:"项目"` } type Context struct { Order *Order `json:"order" label:"当前订单"` OrderNumber int64 `json:"order_number"` Item *OrderItem `json:"item" label:"当前项目"` Calls []Call `json:"calls"` Stack []Stack `json:"stack"` C *gin.Context } func NewContext(order *Order, c *gin.Context) *Context { context := new(Context) context.Order = order context.OrderNumber = -1 context.Calls = make([]Call, 0) context.Stack = make([]Stack, 0) context.C = c return context } func ToDataType(value interface{}, dataType string) (interface{}, bool) { if value != nil { switch dataType { case "number": ret, ok := db.ToFloat64(value) if ok { return ret, true } case "string": return db.ToString(value), true case "array": ret, ok := value.([]interface{}) if !ok { var array interface{} utils.JsonDecode(db.ToString(value)).To(&array) ret, ok = db.ToArray(array) } if ok { return ret, true } } } return nil, false } func (context *Context) Push(item *OrderItem) { context.Stack = append(context.Stack, Stack{context.OrderNumber, context.Item}) context.Item = item context.OrderNumber = -1 } func (context *Context) Pop() { context.Item = context.Stack[len(context.Stack)-1].Item context.OrderNumber = context.Stack[len(context.Stack)-1].OrderNumber context.Stack = context.Stack[:len(context.Stack)-1] } func (context *Context) Get(name string) (interface{}, error) { for _, call := range context.Calls { if call.Item == context.Item && call.Name == name { return nil, errors.New("circular reference: " + name) } } context.Calls = append(context.Calls, Call{name, context.Item}) defer func() { context.Calls = context.Calls[:len(context.Calls)-1] }() item := context.Item for ; item != nil; item = item.Parent { if name == "项目名称" { return item.Item.Name, nil } for _, prop := range item.Item.Props { if name == prop.Name { var value interface{} = nil switch prop.Type { case 0: value = prop.Value case 1: if _, ok := item.From.Results[prop.ID]; !ok { result, err := context.Exec(prop.Value, item, -1) if err != nil { return nil, err } item.From.Results[prop.ID] = result } value = item.From.Results[prop.ID] case 2, 3: value = item.From.Prop[prop.ID] } if ret, ok := ToDataType(value, prop.DataType); ok { return ret, nil } } } if item == context.Item && item.From.Product.ID != 0 { if name == "产品名称" { return item.From.Product.Name, nil } if item.Product != nil { propMap := utils.JsonDecode(item.Product.Property).ToMap() if _, ok := propMap[name]; ok { if number, ok := db.ToFloat64(propMap[name]); ok { return number, nil } return propMap[name], nil } } if _, ok := item.From.Product.Spec[name]; ok { return item.From.Product.Spec[name], nil } } } for _, prop := range context.Order.Calc.Props { if name == prop.Name { var value interface{} = nil switch prop.Type { case 0: value = prop.Value case 1: if _, ok := context.Order.From.Results[prop.ID]; !ok { result, err := context.Exec(prop.Value, item, -1) if err != nil { return nil, err } context.Order.From.Results[prop.ID] = result } value = context.Order.From.Results[prop.ID] case 2, 3: value = context.Order.From.Prop[prop.ID] } if ret, ok := ToDataType(value, prop.DataType); ok { return ret, nil } } } return nil, errors.New("undeclared name: " + name) } func (context *Context) GetArray(args ...interface{}) (interface{}, error) { if len(args) == 0 || len(args) > 3 { return nil, errors.New("not enough arguments in call to call") } var ok bool expr := "" if expr, ok = args[0].(string); !ok { return nil, errors.New("wrong parameter type: call") } cond := "" if len(args) >= 2 { if cond, ok = args[1].(string); !ok { return nil, errors.New("wrong parameter type: call") } } inChild := false if len(args) >= 3 { if inChild, ok = args[2].(bool); !ok { return nil, errors.New("wrong parameter type: call") } } items := context.Order.Items if context.Item != nil { items = context.Item.Childs } var array []interface{} orderNumber := int64(0) for i, _ := range items { if inChild { context.Push(items[i]) ret, err := context.GetArray(expr, cond, inChild) context.Pop() if err == nil { if reta, ok := ret.([]interface{}); ok { array = append(array, reta...) } } } if cond != "" { ret, err := context.Exec(cond, items[i], int64(i)) if err != nil { continue } if retb, ok := ret.(bool); !ok || !retb { continue } } result, err := context.Exec(expr, items[i], orderNumber) orderNumber++ if err == nil { array = append(array, result) } } return array, nil } func (context *Context) ToFloat(value interface{}) float64 { ret, ok := db.ToFloat64(value) if ok { return ret } return 0 } func (context *Context) Exec(expression string, item *OrderItem, orderNumber int64) (interface{}, error) { if item != context.Item { context.Push(item) context.OrderNumber = orderNumber defer context.Pop() } return context.EvaluableExpression(expression) } func (context *Context) EvaluableExpression(expression string) (interface{}, error) { functions := map[string]govaluate.ExpressionFunction{ "call": context.GetArray, "sum": func(args ...interface{}) (interface{}, error) { array, err := context.GetArray(args...) if err != nil { return nil, err } var result float64 for _, item := range array.([]interface{}) { result += context.ToFloat(item) } return result, nil }, "len": func(args ...interface{}) (interface{}, error) { return len(args), nil }, "index": func(args ...interface{}) (interface{}, error) { if len(args) < 2 { return nil, errors.New("not enough arguments in call to call") } index := int(context.ToFloat(args[len(args)-1])) if index >= len(args)-1 { return nil, errors.New("index out of range: index") } return args[index], nil }, "string_slice": func(args ...interface{}) (interface{}, error) { if len(args) == 0 || len(args) > 3 { return nil, errors.New("not enough arguments in call to call") } str := []rune("") if s, ok := args[0].(string); ok { str = []rune(s) } else { return nil, errors.New("wrong parameter type: string_slice") } start := int(0) if len(args) >= 2 { if num, ok := args[1].(float64); ok { start = int(num) } else { return nil, errors.New("wrong parameter type: string_slice") } } if start < 0 { start = len(str) + start } if start < 0 { start = 0 } if start > len(args) { start = len(args) } end := len(str) if len(args) >= 3 { if num, ok := args[2].(float64); ok { end = int(num) } else { return nil, errors.New("wrong parameter type: string_slice") } } if end < 0 { end = len(str) + end } if end < 0 { end = 0 } if end > len(args) { end = len(args) } return string(str[start:end]), nil }, "count": func(args ...interface{}) (interface{}, error) { array, err := context.GetArray(args...) if err != nil { return nil, err } return len(array.([]interface{})), nil }, "sub": func(args ...interface{}) (interface{}, error) { var ok bool expr := "" if expr, ok = args[0].(string); !ok { return nil, errors.New("wrong parameter type: call") } cond := "" if len(args) >= 2 { if cond, ok = args[1].(string); !ok { return nil, errors.New("wrong parameter type: call") } } num := int64(0) if len(args) >= 3 { if _, ok = args[2].(float64); !ok { return nil, errors.New("wrong parameter type: call") } num = int64(args[2].(float64)) } array, err := context.GetArray(expr, cond, false) if err != nil { return nil, err } if num < 0 { num = int64(len(array.([]interface{}))) + num } if num < 0 { num = 0 } if num >= int64(len(array.([]interface{}))) { num = int64(len(array.([]interface{}))) - 1 } if num < 0 { return 0, nil } return array.([]interface{})[num], nil }, "name": func(args ...interface{}) (interface{}, error) { if context.Item == nil { return context.Order.Calc.Name, nil } return context.Item.Item.Name, nil }, "product": func(args ...interface{}) (interface{}, error) { if context.Item.From.Product.ID != 0 { return context.Item.From.Product.Name, nil } return "", nil }, "order": func(args ...interface{}) (interface{}, error) { if context.OrderNumber < 0 { if context.Item == nil { return 0, nil } return float64(context.Item.OrderNumber), nil } return float64(context.OrderNumber), nil }, "unit": func(args ...interface{}) (interface{}, error) { var ok bool name := "" if name, ok = args[0].(string); !ok { return nil, errors.New("wrong parameter type: call") } item := context.Item for ; item != nil; item = item.Parent { for _, prop := range item.Item.Props { if name == prop.Name { return prop.Unit, nil } } } for _, prop := range context.Order.Calc.Props { if name == prop.Name { return prop.Unit, nil } } return "", nil }, "author": func(args ...interface{}) (interface{}, error) { type AdminInfo struct { ID int `json:"id"` UserName string `json:"username"` Phone string `json:"phone"` } var adminInfo AdminInfo _, err := admin.GetInfoByID(int(context.Order.AdminId), []string{"id", "username", "phone"}, &adminInfo) if err != nil { return "", nil } return adminInfo.UserName, nil }, "round": func(args ...interface{}) (interface{}, error) { var ok bool value := float64(0) if value, ok = args[0].(float64); !ok { return nil, errors.New("wrong parameter type: round") } precision := float64(0) if len(args) >= 2 { if precision, ok = args[1].(float64); !ok { return nil, errors.New("wrong parameter type: round") } } p := math.Pow10(int(precision)) return math.Floor(value*p+0.5) / p, nil }, } expr, err := govaluate.NewEvaluableExpressionWithFunctions(expression, functions) if err != nil { if context.Item == nil { logger.Sugar.Infof("expr: %v error: %v", expression, err.Error()) } else { logger.Sugar.Infof("item: %v %v expr: %v error: %v", context.Item.Item.ID, context.Item.Item.Name, expression, err.Error()) } return nil, err } result, err := expr.Eval(context) if err != nil { if context.Item == nil { logger.Sugar.Infof("expr: %v error: %v", expression, err.Error()) } else { logger.Sugar.Infof("item: %v %v expr: %v error: %v", context.Item.Item.ID, context.Item.Item.Name, expression, err.Error()) } return nil, err } return result, nil } func (context *Context) Eval() error { if context.Item == nil { for _, item := range context.Order.Items { context.Push(item) err := context.Eval() context.Pop() if err != nil { return err } } for _, prop := range context.Order.Calc.Props { if prop.Type == 1 { if _, ok := context.Order.From.Results[prop.ID]; !ok { result, err := context.EvaluableExpression(prop.Value) if err != nil { return err } if context.Order.From.Results[prop.ID], ok = ToDataType(result, prop.DataType); !ok { return errors.New("wrong property type") } } } } } else { for _, item := range context.Item.Childs { context.Push(item) err := context.Eval() context.Pop() if err != nil { return err } } for _, prop := range context.Item.Item.Props { if prop.Type == 1 { if _, ok := context.Item.From.Results[prop.ID]; !ok { result, err := context.EvaluableExpression(prop.Value) if err != nil { return err } context.Item.From.Results[prop.ID] = result } } } } return nil }