package utils import ( "fmt" "sort" "strings" "github.com/gogf/gf/v2/util/gconv" ) // ============================================================ // 业务字段读写:TakeBusinessFields 把 businessParams 按映射解析为写入路径, // WriteBusinessFields 按路径写入最终请求体(路径语法见 SetByPath)。 // ============================================================ // TakeBusinessFields 把业务参数(businessParams)按映射解析为写入路径: // - 调用方按业务字段名(RequestBusinessFieldMapping 的 key)传值,这里是独立的 businessParams map, // 不再与模板字段混在 requestParams 中 // - 业务字段名未配置映射 → 返回错误(不静默忽略) // - 解包 {type,value} 包裹格式为原始值 // - 跳过空值(空串/空数组),避免写入请求体污染 // // 返回 map[映射路径]原始值,构建完成后由 WriteBusinessFields 按路径写入请求体。 func TakeBusinessFields(businessParams map[string]any, bizMapping map[string]string) (map[string]any, error) { if len(businessParams) == 0 { return nil, nil } keyToPath := make(map[string]string, len(bizMapping)) for key, path := range bizMapping { if key != "" && path != "" { keyToPath[key] = path } } bizValues := make(map[string]any) for key, raw := range businessParams { path, isBiz := keyToPath[key] if !isBiz { return nil, fmt.Errorf("业务字段 [%s] 未配置映射(RequestBusinessFieldMapping 中不存在该业务字段名)", key) } v := unwrapBizValue(raw) if isEmptyBizValue(v) { continue } bizValues[path] = v } return bizValues, nil } // unwrapBizValue 解包模板包裹格式 {type, value},返回原始值;非包裹格式原样返回 func unwrapBizValue(v any) any { if m, ok := v.(map[string]any); ok { if _, hasType := m["type"]; hasType { if val, hasVal := m["value"]; hasVal { return val } } } return v } // isEmptyBizValue 判断业务字段值是否为空(空值不写入请求体) func isEmptyBizValue(v any) bool { switch t := v.(type) { case nil: return true case string: return t == "" case []any: return len(t) == 0 case map[string]any: return len(t) == 0 } return false } // ============================================================ // 统一字段路径语法(读/写共用,见 NormalizeFieldPath): // // a.b.c 普通点号路径 // a[*].b [*] 表示数组段 // a[*].b[*]?k=v&t=# 单层选择器:在数组中按 k==v 匹配元素,值/读取目标为 t // a[*]?k=v&b[*]?k2=v2&t=# 多级选择器:选择器体内可再嵌 [*]?选择器,级数不限。 // 每级 k=v 既是匹配条件(命中已存在元素时), // 也是新建元素时写入该元素的字段(如 role=user 直接落为 role 字段); // 只有带 t=# 的那级是叶子目标(写值/读值的位置)。 // // SetByPath(写,构建请求体)与 GetByPath(读,解析响应)共用 parsePath; // 读方向语义:数组段非末段取第 0 个元素继续下钻,[*] 为末段返回整个数组,选择器定位匹配元素; // 写方向语义:数组段非末段作用于最后一个元素,末段前置追加(业务值在前),选择器 upsert(命中更新/未命中新建), // 多值([]any)仅在叶子选择器展开为多个独立元素(多个参考图/视频等)。 // ============================================================ // SetByPath 按业务字段映射路径把值写入请求结构(请求侧构建)。 // 路径语法与 BuildSchemaMapping 输出一致(干净形态,无需 attrs 剔除)。 // // 写入语义(业务字段一律前置追加,不覆盖已有值): // - 目标字段已存在且是数组 → 业务值(或其元素)前置插入数组头部,原元素依次后移 // - 目标字段已存在且非数组(普通叶子路径)→ 业务值前置拼接(字符串拼接/数组包裹),原值保留在后 // - 目标字段不存在 → 新建 // - 数组段/选择器段目标字段不是数组(如字符串 content)→ 返回错误,不覆盖已有值 // - 中间路径遇到非对象字段 → 返回错误 // - 数组段无选择器且非末尾 → 作用于最后一个元素(追加语义),数组为空则补一个空元素 // - 数组段带选择器 → 命中则更新目标字段,未命中则按选择器字段构造新元素追加; // 选择器段即使未标 [*] 也按数组处理(如 input.media?type=first_frame&url=#) // - 多级选择器 → 递归:中间级选择器定位/新建容器元素并继续下钻,叶子选择器写值 // - 值本身是数组 → 叶子选择器逐值追加;普通数组段/点号路径前置追加(业务值在前,原值保留在后,不覆盖) func SetByPath(root map[string]any, path string, value any) error { steps := parsePath(NormalizeFieldPath(path)) if len(steps) == 0 { return nil } return setBySteps(root, steps, value) } // setBySteps 按步骤序列写入;选择器步骤(可能带嵌套)递归处理,非选择器步骤逐层下钻 func setBySteps(cur map[string]any, steps []step, value any) error { first := steps[0] last := len(steps) == 1 if first.sel != nil { // 选择器段:目标字段按数组处理(upsert),路径段未标 [*] 也按数组匹配 arr, err := existingArray(cur, first.key) if err != nil { return err } newArr, err := upsertStep(arr, first, value) if err != nil { return err } cur[first.key] = newArr return nil } if !first.isArray { if last { setLeaf(cur, first.key, value) return nil } next, err := ensureMap(cur, first.key) if err != nil { return err } return setBySteps(next, steps[1:], value) } // 数组段(无选择器) arr, err := existingArray(cur, first.key) if err != nil { return err } if last { cur[first.key] = prependToArray(arr, value) return nil } // 无选择器数组段:作用于最后一个元素(追加语义) if len(arr) == 0 { arr = append(arr, map[string]any{}) cur[first.key] = arr } lastElem, ok := arr[len(arr)-1].(map[string]any) if !ok { return fmt.Errorf("字段 [%s] 数组元素不是对象,无法继续下钻(当前类型 %T)", first.key, arr[len(arr)-1]) } return setBySteps(lastElem, steps[1:], value) } // WriteBusinessFields 把业务字段值写入最终请求体。 // bizValues 的键为映射路径(如 input.media?type=reference_video&url=#),值由调用方按路径传入。 // 按字典序升序写入:父路径是子路径的前缀(短者靠前),保证容器先写、子路径再 upsert, // 避免子路径先建出的结构被父路径整体覆盖(如 messages 容器与 messages[*].content[*] 内嵌目标并存)。 // 任一路径写入失败(如数组段目标不是数组)→ 返回错误,由调用方拒绝本次请求。 func WriteBusinessFields(out map[string]any, bizValues map[string]any) error { paths := make([]string, 0, len(bizValues)) for path := range bizValues { paths = append(paths, path) } sort.Strings(paths) for _, path := range paths { if err := SetByPath(out, path, bizValues[path]); err != nil { return fmt.Errorf("业务字段写入失败 [%s]: %w", path, err) } } return nil } // GetByPath 按字段路径读取响应值(与 SetByPath 同一套路径语法,读方向语义): // - 普通段:逐层进入对象取字段 // - 数组段 [*]:非末段取数组第 0 个元素继续下钻;[*] 为末段返回整个数组 // - 选择器段 ?k=v&t=#:定位 k==v 的元素,返回该元素 t 字段的值;多级选择器递归下钻 // // 未命中(路径缺失 / 中间类型不符)返回 (nil, nil),不视为错误;语法错误返回 error。 func GetByPath(root map[string]any, path string) (any, error) { steps := parsePath(NormalizeFieldPath(path)) if len(steps) == 0 { return nil, nil } return getBySteps(root, steps) } // getBySteps 按步骤序列读取;选择器步骤(可能带嵌套)递归处理 func getBySteps(cur any, steps []step) (any, error) { if len(steps) == 0 { return nil, nil } first := steps[0] rest := steps[1:] m, ok := cur.(map[string]any) if !ok { return nil, nil } // 选择器段:定位匹配元素,返回叶子目标或递归嵌套下钻 if first.sel != nil { arr, ok := m[first.key].([]any) if !ok { return nil, nil } for _, e := range arr { em, ok := e.(map[string]any) if !ok { continue } if matchFilters(em, first.sel) { return getSelValue(em, first.sel, rest) } } return nil, nil } v, ok := m[first.key] if !ok { return nil, nil } if first.isArray { arr, ok := v.([]any) if !ok { return nil, nil } if len(rest) == 0 { return arr, nil } if len(arr) == 0 { return nil, nil } return getBySteps(arr[0], rest) } if len(rest) == 0 { return v, nil } return getBySteps(v, rest) } // getSelValue 选择器命中元素后取值:有嵌套路径则递归下钻,否则取叶子目标字段 // (target 可为点号路径,如 image_url.url=#,按 parseSteps 拆级下钻) func getSelValue(em map[string]any, sel *selNode, rest []step) (any, error) { if len(sel.nested) > 0 { return getBySteps(em, append(sel.nested, rest...)) } if sel.target != "" { return getBySteps(em, append(parseSteps(sel.target), rest...)) } return getBySteps(em, rest) } // GetByPathValue 读取路径值,未命中或出错返回 nil(免去调用方处理双返回值) func GetByPathValue(root map[string]any, path string) any { v, err := GetByPath(root, path) if err != nil { return nil } return v } // GetByPathAll 按字段路径读取响应值(与 GetByPath 同一套语法),返回路径下**所有**命中值。 // 与 GetByPath 的区别:GetByPath 命中即返回第一个匹配;GetByPathAll 遍历数组段/选择器段的全部 // 元素并展开收集。适用于通配路径(messages[*]...[*]...)取全部匹配值(如收集所有图片 url)。 // 无命中返回 nil。 func GetByPathAll(root map[string]any, path string) []any { steps := parsePath(NormalizeFieldPath(path)) if len(steps) == 0 { return nil } return getAllBySteps(root, steps) } // getAllBySteps 按步骤序列收集全部匹配值;数组段/选择器段遍历所有元素展开,普通段单值包裹返回 func getAllBySteps(cur any, steps []step) []any { if len(steps) == 0 { return nil } first := steps[0] rest := steps[1:] m, ok := cur.(map[string]any) if !ok { return nil } // 选择器段:遍历命中元素收集 if first.sel != nil { arr, ok := m[first.key].([]any) if !ok { return nil } var out []any for _, e := range arr { em, ok := e.(map[string]any) if !ok { continue } if matchFilters(em, first.sel) { out = append(out, getSelValueAll(em, first.sel, rest)...) } } return out } v, ok := m[first.key] if !ok { return nil } if first.isArray { arr, ok := v.([]any) if !ok { return nil } if len(rest) == 0 { return arr } var out []any for _, e := range arr { out = append(out, getAllBySteps(e, rest)...) } return out } if len(rest) == 0 { return []any{v} } return getAllBySteps(v, rest) } // getSelValueAll 选择器命中元素后收集:有嵌套路径递归下钻,否则取叶子目标字段(全部) func getSelValueAll(em map[string]any, sel *selNode, rest []step) []any { if len(sel.nested) > 0 { return getAllBySteps(em, append(sel.nested, rest...)) } if sel.target != "" { return getAllBySteps(em, append(parseSteps(sel.target), rest...)) } return getAllBySteps(em, rest) } // step 路径段;sel 非空表示该段带选择器(按数组处理) type step struct { key string isArray bool sel *selNode } // selNode 选择器: // - filters:k=v 匹配条件,新建元素时也作为字段写入 // - target:叶子目标字段(k=#),值/读取目标;target 为空且 nested 非空时为中间级选择器 // - nested:下钻子路径(多级嵌套选择器,级数不限) type selNode struct { filters [][2]string target string nested []step } // parsePath 解析路径为步骤序列。选择器体挂到最后一个步骤上;选择器体中的嵌套 [*]?选择器 // 递归解析为 nested(级数不限)。 func parsePath(p string) []step { base, suffix := p, "" if i := strings.Index(p, "?"); i >= 0 { base, suffix = p[:i], p[i+1:] } steps := parseSteps(base) if suffix != "" { parseSelector(suffix, &steps) } return steps } // parseSteps 解析点号分隔的普通步骤(含 [*] 数组段) func parseSteps(s string) []step { var steps []step for _, raw := range strings.Split(s, ".") { raw = strings.TrimSpace(raw) if raw == "" { continue } st := step{} if strings.HasSuffix(raw, "[*]") { st.key = strings.TrimSuffix(raw, "[*]") st.isArray = true } else { st.key = raw } steps = append(steps, st) } return steps } // parseSelector 解析选择器体(? 之后的内容)并挂到最后一个步骤上。 // 元素用顶层 & 分隔(? 之后的 & 属于嵌套选择器);k=v 为过滤/写入对,k=# 为叶子目标, // 含 [*] 或路径的块为嵌套下钻子路径(递归 parsePath)。 func parseSelector(selStr string, steps *[]step) { if len(*steps) == 0 { return } sel := &selNode{} var nested []step for _, el := range splitTopLevel(selStr) { if isPair(el) { k, v, _ := strings.Cut(el, "=") if v == "#" { sel.target = k } else { sel.filters = append(sel.filters, [2]string{k, v}) } continue } // 嵌套路径(含自己的选择器):级数不限,递归解析 nested = append(nested, parsePath(el)...) } if len(sel.filters) == 0 && sel.target == "" && len(nested) == 0 { return } last := &(*steps)[len(*steps)-1] if last.sel == nil { last.sel = sel } if len(nested) > 0 { last.sel.nested = nested } } // splitTopLevel 按顶层 & 拆分选择器体;? 之后的 & 属于嵌套选择器,不在此层拆分 func splitTopLevel(s string) []string { var elems []string var cur strings.Builder inNested := false for _, ch := range s { if ch == '?' { inNested = true } if ch == '&' && !inNested { elems = append(elems, cur.String()) cur.Reset() continue } cur.WriteRune(ch) } if cur.Len() > 0 { elems = append(elems, cur.String()) } return elems } // isPair 判断元素是否为 k=v 对:= 出现在任何 [ ? 之前则是 pair,否则为嵌套路径。 // 目标字段 k 本身可以是点号路径(image_url.url=#),故 . 不参与判别。 func isPair(el string) bool { for i := 0; i < len(el); i++ { switch el[i] { case '=': return true case '[', '?': return false } } return false } // existingArray 返回数组字段的当前数组: // - 字段不存在 → 空数组(允许按追加语义新建) // - 字段是数组 → 原样 // - 字段是其他类型(如字符串 content)→ 返回错误,调用方拒绝写入,不覆盖已有值 func existingArray(cur map[string]any, key string) ([]any, error) { v, ok := cur[key] if !ok { return []any{}, nil } if arr, ok := v.([]any); ok { return arr, nil } return nil, fmt.Errorf("字段 [%s] 不是数组,无法按数组路径写入(当前类型 %T)", key, v) } // upsertStep 选择器 upsert,返回追加后的数组: // - 叶子选择器(有 target)且值为数组 → 每个值追加一个独立元素 // - 命中(所有 filters 匹配)→ 把值写入现有元素(叶子写 target,中间级递归 nested) // - 未命中 → 按选择器字段构造新元素并追加 // // 返回新切片(append 可能重新分配底层数组),调用方需用返回值覆盖写回。 func upsertStep(arr []any, st step, value any) ([]any, error) { sel := st.sel // 叶子选择器:多值逐个展开为独立元素(多个参考图/视频等) if sel.target != "" { if vals, ok := value.([]any); ok && len(vals) > 0 { for _, v := range vals { elem, err := buildStepElement(sel, v) if err != nil { return nil, err } arr = append(arr, elem) } return arr, nil } } for _, e := range arr { m, ok := e.(map[string]any) if !ok { continue } if matchFilters(m, sel) { if err := writeStepValue(m, sel, value); err != nil { return nil, err } return arr, nil } } elem, err := buildStepElement(sel, value) if err != nil { return nil, err } return append(arr, elem), nil } // matchFilters 判断元素是否匹配选择器全部过滤条件;无过滤条件时命中第一个元素 func matchFilters(m map[string]any, sel *selNode) bool { if len(sel.filters) == 0 { return true } for _, f := range sel.filters { if gconv.String(m[f[0]]) != f[1] { return false } } return true } // writeStepValue 把值写入已定位元素:中间级递归 nested 下钻,叶子写 target 字段 func writeStepValue(m map[string]any, sel *selNode, value any) error { if len(sel.nested) > 0 { return setBySteps(m, sel.nested, value) } if sel.target != "" { return setLeafPath(m, sel.target, value) } return nil } // buildStepElement 按选择器构造新元素:{filterKey: filterVal, ...} + 叶子写 target / 中间级递归 nested func buildStepElement(sel *selNode, value any) (map[string]any, error) { elem := make(map[string]any, len(sel.filters)+1) for _, f := range sel.filters { elem[f[0]] = f[1] } if err := writeStepValue(elem, sel, value); err != nil { return nil, err } return elem, nil } // setLeafPath 在对象内按点号路径写入值(叶子用 setLeaf 语义) func setLeafPath(m map[string]any, path string, value any) error { cur := m segs := strings.Split(path, ".") for i, k := range segs { if i == len(segs)-1 { setLeaf(cur, k, value) return nil } next, err := ensureMap(cur, k) if err != nil { return err } cur = next } return nil } // setLeaf 叶子写入(业务字段语义:值前置追加而非覆盖): // 字段不存在 → 直接写入;已有值 → 业务字段值前置到原有值前面(数组前插/字符串拼接),原值保留在后。 func setLeaf(parent map[string]any, key string, value any) { existing, ok := parent[key] if !ok { parent[key] = value return } parent[key] = prependValue(value, existing) } // prependValue 把 value 前置到 existing 前(业务值在前,原值保留在后,不覆盖): // - existing 是数组 → value(或其元素)前插到数组头部 // - value 是数组(existing 为标量)→ value 各元素在前,existing 作为末位元素 // - 其余标量 → 字符串拼接,业务值在前 func prependValue(value, existing any) any { if arr, isArr := existing.([]any); isArr { return prependToArray(arr, value) } if vals, isArr := value.([]any); isArr { out := make([]any, 0, len(vals)+1) out = append(out, vals...) out = append(out, existing) return out } return gconv.String(value) + gconv.String(existing) } // prependToArray 把 value(或其元素)插到数组头部,原元素依次后移 func prependToArray(arr []any, value any) []any { if vals, isArr := value.([]any); isArr { out := make([]any, 0, len(vals)+len(arr)) out = append(out, vals...) out = append(out, arr...) return out } out := make([]any, 0, len(arr)+1) out = append(out, value) out = append(out, arr...) return out } // ensureMap 确保键对应 map,不存在则新建;已存在但非对象 → 返回错误 func ensureMap(parent map[string]any, key string) (map[string]any, error) { if v, ok := parent[key]; ok { if m, isMap := v.(map[string]any); isMap { return m, nil } return nil, fmt.Errorf("字段 [%s] 不是对象,无法按路径写入(当前类型 %T)", key, v) } m := map[string]any{} parent[key] = m return m, nil }