package service import ( "context" "fmt" "strings" "model-gateway/dao" "model-gateway/model/dto" "model-gateway/model/entity" "github.com/gogf/gf/v2/util/gconv" ) // ====================== 模型引用解析层 ====================== // 引用行(ref_system_model_id>0) 调用时实时取系统模型配置 + 本人 apiKey 合成可执行配置; // 系统模型调整零同步。Id 被覆盖为系统模型 id → 计价/并发键按系统模型走(会话/任务落库仍用引用行 id)。 const apiKeyPlaceholder = "{apiKey}" // replacePlaceholder 递归替换 map/slice/string 中的占位符(泛化自 task_end 的 replaceTaskPlaceholder,非破坏式)。 func replacePlaceholder(v any, from, to string) any { switch val := v.(type) { case string: return strings.ReplaceAll(val, from, to) case map[string]any: m := make(map[string]any, len(val)) for k, x := range val { m[k] = replacePlaceholder(x, from, to) } return m case map[string]string: m := make(map[string]string, len(val)) for k, x := range val { m[k] = strings.ReplaceAll(x, from, to) } return m case []any: arr := make([]any, len(val)) for i, x := range val { arr[i] = replacePlaceholder(x, from, to) } return arr default: return v } } // copyAndReplaceStringMap 非破坏式替换 map[string]string 值(新建 map,不污染入参) func copyAndReplaceStringMap(src map[string]string, from, to string) map[string]string { if src == nil { return nil } m := make(map[string]string, len(src)) for k, v := range src { m[k] = strings.ReplaceAll(v, from, to) } return m } // substituteAPIPlaceholder 把输入侧配置中的 {apiKey} 替换为生效 key(非破坏式:新建 map/struct,不污染入参)。 // 覆盖 BaseURL / RequestHeadMapping / RequestBodyMapping / RequestBusinessFieldMapping / // AsyncTaskMapping(Url/RequestHeadMapping/RequestBodyMapping)。 func substituteAPIPlaceholder(m *entity.ModelManage, key string) { m.BaseURL = strings.ReplaceAll(m.BaseURL, apiKeyPlaceholder, key) m.RequestHeadMapping = copyAndReplaceStringMap(m.RequestHeadMapping, apiKeyPlaceholder, key) m.RequestBusinessFieldMapping = copyAndReplaceStringMap(m.RequestBusinessFieldMapping, apiKeyPlaceholder, key) if v, ok := replacePlaceholder(m.RequestBodyMapping, apiKeyPlaceholder, key).(map[string]any); ok { m.RequestBodyMapping = v } if a := m.AsyncTaskMapping; a != nil { ac := *a ac.Url = strings.ReplaceAll(a.Url, apiKeyPlaceholder, key) ac.RequestHeadMapping = copyAndReplaceStringMap(a.RequestHeadMapping, apiKeyPlaceholder, key) if v, ok := replacePlaceholder(a.RequestBodyMapping, apiKeyPlaceholder, key).(map[string]any); ok { ac.RequestBodyMapping = v } m.AsyncTaskMapping = &ac } } // mergeReferenceConfig 引用行 + 系统行 → 有效配置(纯函数,便于单测)。 // 配置字段取系统行;个人字段(apiKey/enabled/chatModel)取引用行;enabled 取 AND(系统停用=引用失效)。 func mergeReferenceConfig(stub, sys *entity.ModelManage) *entity.ModelManage { out := *sys out.ApiKey = stub.ApiKey if stub.Enabled != nil { out.Enabled = stub.Enabled } if stub.ChatModel != nil { out.ChatModel = stub.ChatModel } if sys.Enabled != nil && !*sys.Enabled { out.Enabled = gconv.PtrBool(false) } return &out } // resolveModelConfig 把请求命中的模型行解析为可执行配置: // 引用行 → 系统行配置 + 引用行 apiKey(Id 覆盖为系统模型 id);非引用行 → 原配置 + 自身 apiKey 替换占位。 // 引用系统模型已删除 → 报错(调用方阻塞)。 func resolveModelConfig(ctx context.Context, m *entity.ModelManage) (*entity.ModelManage, error) { if m == nil { return nil, nil } if m.RefSystemModelId > 0 { sys, err := dao.ModelManage.GetNotTenantId(ctx, &dto.GetModelManageReq{Id: m.RefSystemModelId}) if err != nil { return nil, err } if sys == nil { return nil, fmt.Errorf("引用的系统模型已删除") } out := mergeReferenceConfig(m, sys) substituteAPIPlaceholder(out, out.ApiKey) return out, nil } out := *m substituteAPIPlaceholder(&out, out.ApiKey) return &out, nil }