const ( Eq Op = "eq" In Op = "in" Exists Op = "exists" Range Op = "range" )
// Cond 是一个过滤条件。 type Cond struct { Field string Op Op Values []any }
// Filter 是若干条件的与关系。 type Filter []Cond
// Validate 校验条件是否合法;不认识的条件直接报错,避免静默放宽。 func(f Filter) Validate() error { for i, c := range f { if c.Field == "" { return fmt.Errorf("第 %d 个条件缺少字段", i) } switch c.Op { case Eq: iflen(c.Values) != 1 { return fmt.Errorf("%s: eq 需要 1 个值", c.Field) } case In: iflen(c.Values) == 0 { return fmt.Errorf("%s: in 至少需要 1 个值", c.Field) } case Exists: iflen(c.Values) != 0 { return fmt.Errorf("%s: exists 不需要值", c.Field) } case Range: iflen(c.Values) != 2 { return fmt.Errorf("%s: range 需要 2 个值", c.Field) } default: return fmt.Errorf("%s: 不支持的操作符 %q", c.Field, c.Op) } } returnnil }
// Match 判断文档是否满足全部条件。 func(f Filter) Match(doc map[string]any) bool { if f.Validate() != nil { returnfalse } for _, c := range f { v, ok := lookup(doc, c.Field) switch c.Op { case Eq: if !ok || !equal(v, c.Values[0]) { returnfalse } case In: if !ok || !contains(c.Values, v) { returnfalse } case Exists: if !ok { returnfalse } case Range: if !ok || !inRange(v, c.Values[0], c.Values[1]) { returnfalse } } } returntrue }
这里有一个重要原则:条件不认识时不放宽。
Match 在条件非法时直接返回 false,而不是跳过条件。统计查询宁可少返回,也不能因为条件写错而返回全量数据。
// lookup 按点号路径取值,例如 "order.status"。 funclookup(doc map[string]any, field string) (any, bool) { var cur any = doc for _, part := range strings.Split(field, ".") { m, ok := cur.(map[string]any) if !ok { returnnil, false } if cur, ok = m[part]; !ok { returnnil, false } } return cur, true }
funcequal(a, b any)bool { return fmt.Sprint(a) == fmt.Sprint(b) }
funccontains(values []any, v any)bool { for _, x := range values { if equal(x, v) { returntrue } } returnfalse }
funcinRange(v, min, max any)bool { f, ok := toFloat(v) if !ok { returnfalse } lo, ok := toFloat(min) if !ok { returnfalse } hi, ok := toFloat(max) if !ok { returnfalse } return f >= lo && f <= hi }
// AddDirect 注册直接指标。 func(r *Registry) AddDirect(d Direct) error { if d.Name() == "" { return fmt.Errorf("指标名不能为空") } if d.TimeField() == "" { return fmt.Errorf("指标 %s 缺少时间字段", d.Name()) } if d.Kind() == Sum && d.Field() == "" { return fmt.Errorf("求和指标 %s 缺少字段", d.Name()) } if _, ok := r.directs[d.Name()]; ok { return fmt.Errorf("指标重复注册: %s", d.Name()) } r.directs[d.Name()] = d returnnil }
// AddIndirect 注册间接指标。 func(r *Registry) AddIndirect(i Indirect) error { if i.Name() == "" { return fmt.Errorf("指标名不能为空") } iflen(i.Deps()) == 0 { return fmt.Errorf("指标 %s 至少依赖一个指标", i.Name()) } if _, ok := r.indirects[i.Name()]; ok { return fmt.Errorf("指标重复注册: %s", i.Name()) } r.indirects[i.Name()] = i returnnil }
// Get 按名字返回指标。 func(r *Registry) Get(name string) (any, bool) { if d, ok := r.directs[name]; ok { return d, true } if i, ok := r.indirects[name]; ok { return i, true } returnnil, false }
// Resolve 返回请求指标及其依赖;间接指标按依赖顺序排列。 func(r *Registry) Resolve(names []string) ([]Direct, []Indirect, error) { seen := make(map[string]bool) var directs []Direct var indirects []Indirect
var visit func(string, map[string]bool)error visit = func(name string, stack map[string]bool)error { if seen[name] { returnnil } if stack[name] { return fmt.Errorf("指标依赖成环: %s", name) } if d, ok := r.directs[name]; ok { seen[name] = true directs = append(directs, d) returnnil } ind, ok := r.indirects[name] if !ok { return fmt.Errorf("未知指标: %s", name) } stack[name] = true for _, dep := range ind.Deps() { if err := visit(dep, stack); err != nil { return err } } delete(stack, name) seen[name] = true indirects = append(indirects, ind) returnnil }
for _, name := range names { if err := visit(name, map[string]bool{}); err != nil { returnnil, nil, err } } return directs, indirects, nil }
requested := make(map[string]bool, len(req.Indicators)) for _, name := range req.Indicators { requested[name] = true }
directAt := make(map[string]map[string]float64, len(directs)) keySet := make(map[string]bool) for _, d := range directs { at := make(map[string]float64) for _, b := range raw[d.Name()] { at[b.Key] = b.Value keySet[b.Key] = true } directAt[d.Name()] = at }
keys := make([]string, 0, len(keySet)) for k := range keySet { keys = append(keys, k) } sort.Strings(keys)
result := make(Result, len(req.Indicators)) for _, k := range keys { values := make(map[string]float64, len(directs)+len(indirects)) for _, d := range directs { values[d.Name()] = directAt[d.Name()][k] if requested[d.Name()] { result[d.Name()] = append(result[d.Name()], Point{Key: k, Value: values[d.Name()]}) } } for _, ind := range indirects { values[ind.Name()] = ind.Calc(values) if requested[ind.Name()] { result[ind.Name()] = append(result[ind.Name()], Point{Key: k, Value: values[ind.Name()]}) } } } return result, nil }
for _, name := range []string{"下单量", "支付量", "营收", "支付率", "客单价"} { fmt.Println(name) for _, p := range res[name] { fmt.Printf(" %s %.2f\n", p.Key, p.Value) } } }