package cloud import ( "bytes" _ "embed" "encoding/json" "errors" "fmt" "net/http" "net/url" "regexp" "slices" "strings" "sync" "text/template" "time" "apigo.cc/gojs" "apigo.cc/gojs/goja" "github.com/gorilla/websocket" "github.com/ssgo/config" "github.com/ssgo/httpclient" "github.com/ssgo/log" "github.com/ssgo/u" ) type Action struct { Desc string Config map[string]any Method string Path string RequestFormat string ResponseFormat string Timeout int RequestParams []*RequestParam RequestHeaders []*HeaderParam Response []*ResponseParam SuccessBy string FailedMessageBy string } type FixedAction struct { Action RequiredRequestParams []*RequestParam NotRequiredRequestParams []*RequestParam FuncParams string } type RequestParam struct { Name string Type string Required bool Value any Desc string Sub []*RequestParam } type HeaderParam struct { Name string Value string Desc string } type ResponseParam struct { Name string Type string Desc string Sub []*ResponseParam } type CloudConfig struct { Desc string Config map[string]any RequestFormat string ResponseFormat string Timeout int RequestParams []*RequestParam RequestHeaders []*HeaderParam Response []*ResponseParam Api map[string]*Action } // var encryptdMatcher = regexp.MustCompile(`<\*\*([\w-=]+)\*\*>`) var encryptdMatcher = regexp.MustCompile(`^[\w-=]+$`) var confAes = u.NewAes([]byte("?GQ$0K0GgLdO=f+~L68PLm$uhKr4'=tV"), []byte("VFs7@sK61cj^f?HZ")) var keysIsSet = false func SetSSKey(key, iv []byte) { if !keysIsSet { confAes = u.NewAes(key, iv) keysIsSet = true } } //go:embed export.ts var exportTS string var cloudConfigs map[string]*CloudConfig var fixedCloudConfigs map[string]map[string]*FixedAction var httpClients = map[string]map[int]*httpclient.ClientPool{} var httpClientsLock = sync.RWMutex{} func init() { gojs.Register("apigo.cc/cloud", gojs.Module{ ObjectMaker: func(vm *goja.Runtime) gojs.Map { logger := gojs.GetLogger(vm) makeConfig(logger) gojsObj := gojs.Map{} for cloudName, actions := range fixedCloudConfigs { httpClientsLock.RLock() hcSet := httpClients[cloudName] httpClientsLock.RUnlock() if hcSet == nil { hcSet = map[int]*httpclient.ClientPool{} httpClientsLock.Lock() httpClients[cloudName] = hcSet httpClientsLock.Unlock() } cloudObj := map[string]any{} for actionName, action := range actions { httpClientsLock.RLock() _, hcOK := hcSet[action.Timeout] httpClientsLock.RUnlock() if !hcOK { hc := httpclient.GetClient(time.Duration(action.Timeout) * time.Millisecond) httpClientsLock.Lock() hcSet[action.Timeout] = hc httpClientsLock.Unlock() } cloudObj[actionName] = makeAction(cloudName, action) } gojsObj[cloudName] = cloudObj } return gojsObj }, TsCodeMaker: func() string { makeConfig(log.DefaultLogger) var err error tsCode := "" tpl := template.New("export") tpl = tpl.Funcs(template.FuncMap{ "makeMap": func(args ...any) map[string]any { out := map[string]any{} for i := 0; i < len(args); i += 2 { out[u.String(args[i])] = args[i+1] } // fmt.Println(u.BGreen(u.JsonP(out))) return out }, }) if tpl, err = tpl.Parse(strings.ReplaceAll(exportTS, "//----", "")); err == nil { buf := bytes.NewBuffer(make([]byte, 0)) if err = tpl.Execute(buf, fixedCloudConfigs); err == nil { tsCode = buf.String() } else { fmt.Println(u.BRed(err.Error())) } } else { fmt.Println(u.BRed(err.Error())) } return tsCode }, SetSSKey: SetSSKey, }) } func getHttpClient(cloudName string, timeout int) *httpclient.ClientPool { httpClientsLock.RLock() defer httpClientsLock.RUnlock() return httpClients[cloudName][timeout] } var expressionMatcher = regexp.MustCompile(`(?ms){{.+?}}`) var funcNameExcludesMatcher = regexp.MustCompile(`[^\w]`) func makeAction(cloudName string, action *FixedAction) any { return func(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value { // 解析参数 requiredCount := len(action.RequiredRequestParams) args := gojs.MakeArgs(&argsIn, vm).Check(requiredCount) params := args.Map(requiredCount + 1) headers := args.Map(requiredCount + 2) // 合并请求参数 for i, param := range action.RequiredRequestParams { params[param.Name] = args.Any(i) } // 处理默认值 for _, param := range action.RequestParams { if param.Value != nil && params[param.Name] == nil { if strValue, ok := param.Value.(string); ok { if !strings.Contains(strValue, "{{") { params[param.Name] = param.Value } } else { params[param.Name] = param.Value } } } for _, header := range action.RequestHeaders { if header.Value != "" && headers[header.Name] == nil { if !strings.Contains(header.Value, "{{") { headers[header.Name] = header.Value } } } // 生成request信息 requestUrl, err := url.Parse(u.String(action.Config["endpoint"]) + action.Path) if err != nil { panic(vm.NewGoError(err)) } paramsSortJoin := func(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value { args := gojs.MakeArgs(&argsIn, vm) joinTag := args.Str(0) linkTag := args.Str(1) if joinTag == "" { joinTag = "&" } if linkTag == "" { linkTag = "=" } a := make([]string, 0) for k, v := range params { a = append(a, k+linkTag+u.String(v)) } slices.Sort(a) return vm.ToValue(strings.Join(a, joinTag)) } if requestUrl.RawPath == "" { requestUrl.RawPath = requestUrl.Path } requestInfo := map[string]any{ "method": action.Method, "scheme": requestUrl.Scheme, "host": requestUrl.Host, "path": requestUrl.Path, "rawPath": requestUrl.RawPath, "rawQuery": requestUrl.RawQuery, "fragment": requestUrl.Fragment, "rawFragment": requestUrl.RawFragment, "fullPath": requestUrl.RequestURI(), "url": requestUrl.String(), "sortJoin": paramsSortJoin, } requestValue := vm.ToValue(requestInfo) // 计算请求参数中的动态值 configValue := vm.ToValue(action.Config) actionValue := vm.ToValue(gojs.MakeMap(action)) var paramsValue goja.Value var headersValue goja.Value var dataValue goja.Value headersValue = vm.ToValue(headers) // 处理基本信息中的动态值 if action.Path != "" && strings.Contains(action.Path, "{{") { action.Path = expressionMatcher.ReplaceAllStringFunc(action.Path, func(s string) string { fnCode := s[2 : len(s)-2] newValue := runFn(fnCode, vm, "action, request, config, params, headers", actionValue, requestValue, configValue, paramsValue, headersValue) return u.String(newValue) }) requestUrl, err = url.Parse(u.String(action.Config["endpoint"]) + action.Path) if err != nil { panic(vm.NewGoError(err)) } if requestUrl.RawPath == "" { requestUrl.RawPath = requestUrl.Path } requestInfo = map[string]any{ "method": action.Method, "scheme": requestUrl.Scheme, "host": requestUrl.Host, "path": requestUrl.Path, "rawPath": requestUrl.RawPath, "rawQuery": requestUrl.RawQuery, "fragment": requestUrl.Fragment, "rawFragment": requestUrl.RawFragment, "fullPath": requestUrl.RequestURI(), "url": requestUrl.String(), "sortJoin": paramsSortJoin, } requestValue = vm.ToValue(requestInfo) } // 处理请求参数中的动态值 for _, param := range action.RequestParams { if param.Value != nil && params[param.Name] == nil { if strValue, ok := param.Value.(string); ok && strings.Contains(strValue, "{{") { isTest := strings.Contains(param.Name, "TEST__") if isTest { fmt.Println("make param:", u.Cyan(param.Name)) } // 将数据拼接到URL中(部分API在请求中需要计算签名) if action.RequestFormat == "query" || action.RequestFormat == "binary" || action.Method == "GET" || action.Method == "WS" { q := requestUrl.Query() for k, v := range params { q.Set(k, u.String(v)) } requestUrl.RawQuery = q.Encode() requestInfo["rawQuery"] = requestUrl.RawQuery requestInfo["fullPath"] = requestUrl.RequestURI() requestInfo["url"] = requestUrl.String() requestValue = vm.ToValue(requestInfo) } // 计算动态值 var lastRealValue any replaceTimes := 0 newValue := expressionMatcher.ReplaceAllStringFunc(strValue, func(s string) string { replaceTimes++ fnCode := s[2 : len(s)-2] // fnName := "_CLOUD_FN_" + funcNameExcludesMatcher.ReplaceAllString(fnCode, "_") // fnValue := vm.Get(fnName) // if fnValue == nil { // if _, err := vm.RunString("function " + fnName + "(action, request, config, params, headers){return " + fnCode + "}"); err != nil { // panic(vm.NewGoError(err)) // } // fnValue = vm.Get(fnName) // } // if fn, ok := goja.AssertFunction(fnValue); ok { // if r, err := fn(nil, actionValue, requestValue, configValue, vm.ToValue(params), vm.ToValue(headers)); err != nil { // panic(vm.NewGoError(err)) // } else { // lastRealValue = r.Export() // if isTest { // fmt.Println(" >", u.BMagenta(s), u.BCyan(u.JsonP(lastRealValue)), ".") // } // return u.String(r.Export()) // } // } else { // panic(vm.NewGoError(errors.New("failed to make function " + fnName))) // } paramsValue = vm.ToValue(params) lastRealValue = runFn(fnCode, vm, "action, request, config, params, headers", actionValue, requestValue, configValue, paramsValue, headersValue) if isTest { fmt.Println(" >", u.BMagenta(s), u.BCyan(lastRealValue), ".") } return u.String(lastRealValue) }) if replaceTimes == 1 && newValue == u.String(lastRealValue) { // 如果是单一公式,直接使用最后一次替换真实类型的结果 if !isTest { params[param.Name] = lastRealValue } } else { // 替换为计算的结果 if !isTest { params[param.Name] = newValue } else { // 打印调试信息 fmt.Println(" >", u.Cyan(u.JsonP(newValue)), ".") fmt.Println() } } } } } paramsValue = vm.ToValue(params) // 根据RequestFormat处理请求数据 if action.RequestFormat == "query" || action.RequestFormat == "binary" || action.Method == "GET" || action.Method == "WS" { // 将数据拼接到URL中 q := requestUrl.Query() for k, v := range params { q.Set(k, u.String(v)) } requestUrl.RawQuery = q.Encode() requestInfo["rawQuery"] = requestUrl.RawQuery requestInfo["fullPath"] = requestUrl.RequestURI() requestInfo["url"] = requestUrl.String() requestValue = vm.ToValue(requestInfo) } var data any switch action.RequestFormat { case "query": // 将数据拼接到URL中 case "binary": // 将数据以二进制形式POST,参数拼接到URL中 args.Check(requiredCount + 1) data = args.Bytes(requiredCount) case "form", "upload": // 将数据以表单类型提交 args.Check(requiredCount + 1) form := make(map[string]string, 0) for k, v := range params { form[k] = u.String(v) } data = form case "json": // 将数据以JSON格式提交 data = u.Json(params) default: if action.Method == "GET" { // 将数据拼接到URL中 } else { // 将数据以JSON格式提交 data = params } } // data = `{"EngSerViceType":"8k_zh","SourceType":1,"VoiceFormat":"mp3","Data":"AA","DataLen":0,"WordInfo":0,"FilterDirty":0,"FilterModal":0,"FilterPunc":0,"ConvertNumMode":1,"CustomizationId":"","InputSampleRate":0}` dataValue = vm.ToValue(data) // 计算请求头中的动态值 for _, header := range action.RequestHeaders { if header.Value != "" && headers[header.Name] == nil { if strings.Contains(header.Value, "{{") { isTest := strings.Contains(header.Name, "TEST__") if isTest { fmt.Println("make header:", u.Cyan(header.Name)) } var lastRealValue any replaceTimes := 0 newValue := expressionMatcher.ReplaceAllStringFunc(header.Value, func(s string) string { replaceTimes++ fnCode := s[2 : len(s)-2] // fnName := "_CLOUD_FN_" + funcNameExcludesMatcher.ReplaceAllString(fnCode, "_") // fnValue := vm.Get(fnName) // if fnValue == nil { // if _, err := vm.RunString("function " + fnName + "(action, request, config, params, headers, data){return " + fnCode + "}"); err != nil { // panic(vm.NewGoError(err)) // } // fnValue = vm.Get(fnName) // } // if fn, ok := goja.AssertFunction(fnValue); ok { // if r, err := fn(nil, actionValue, requestValue, configValue, vm.ToValue(params), vm.ToValue(headers), vm.ToValue(data)); err != nil { // panic(vm.NewGoError(err)) // } else { // lastRealValue = r.Export() // if isTest { // fmt.Println(" >", u.BMagenta(s), u.BCyan(u.JsonP(lastRealValue)), ".") // } // return u.String(lastRealValue) // } // } else { // panic(vm.NewGoError(errors.New("failed to make function " + fnName))) // } headersValue = vm.ToValue(headers) lastRealValue = runFn(fnCode, vm, "action, request, config, params, headers, data", actionValue, requestValue, configValue, paramsValue, headersValue, dataValue) if isTest { fmt.Println(" >", u.BMagenta(s), u.BCyan(lastRealValue), ".") } // if header.Name == "TEST__待签名" { // fmt.Println(u.BGreen(u.Json(lastRealValue)), ".") // fmt.Println(u.BGreen(hex.EncodeToString(u.Sha256([]byte(u.String(lastRealValue)))))) // } return u.String(lastRealValue) }) if replaceTimes == 1 && newValue == u.String(lastRealValue) { // 如果是单一公式,直接使用最后一次替换真实类型的结果 if !isTest { headers[header.Name] = lastRealValue } } else { // 替换为计算的结果 if !isTest { headers[header.Name] = newValue } else { // 打印调试信息 fmt.Println(" >", u.Cyan(u.JsonP(newValue)), ".") fmt.Println() } } } } } // TODO 支持SSE // TODO 支持Download(异步回调) // 处理请求头 headersArr := make([]string, 0) for k, v := range headers { headersArr = append(headersArr, k, u.String(v)) } // 发送请求 hc := getHttpClient(cloudName, action.Timeout) var httpResult *httpclient.Result var otherResult any t1 := time.Now().UnixMilli() if action.RequestFormat == "upload" { // 处理上传文件 files := args.Map(requiredCount) httpResult, _ = hc.MPost(requestUrl.String(), data.(map[string]string), files, headersArr...) } else if action.Method == "WS" { // 处理WS请求 args.Check(requiredCount + 1) callback := args.Func(requiredCount) reqHeader := http.Header{} for k, v := range headers { reqHeader.Set(k, u.String(v)) } if conn, resp, err := websocket.DefaultDialer.Dial(strings.Replace(requestUrl.String(), "http", "ws", 1), reqHeader); err == nil { for { if conn != nil { var data any var msgType string if typ, buf, err := conn.ReadMessage(); err == nil { switch typ { case websocket.TextMessage: msgType = "text" if action.ResponseFormat == "json" { obj := map[string]any{} if err := json.Unmarshal(buf, &obj); err == nil { msgType = "json" data = obj } else { data = string(buf) } } case websocket.BinaryMessage: msgType = "binary" data = buf default: msgType = "unknown" data = string(buf) } } else { break } if msgType == action.ResponseFormat { // 最后一个类型相同的消息作为输出 otherResult = data } if callback != nil { if r, err := callback(nil, vm.ToValue(data), vm.ToValue(msgType)); err != nil { conn.Close() panic(vm.NewGoError(err)) } else if u.Bool(r.Export()) { fmt.Println(222, r.Export()) conn.Close() break } else { fmt.Println(333, r.Export()) } } } else { break } } httpResult = &httpclient.Result{ Response: resp, Error: err, } } else { panic(vm.NewGoError(err)) } } else { // fmt.Println("request", action.Method, requestUrl.String(), u.Json(headersArr), "...") httpResult = hc.Do(action.Method, requestUrl.String(), data, headersArr...) // fmt.Println("response", result.Response.StatusCode, result.Response.Status, result.String(), "...") } if httpResult.Error != nil { panic(vm.NewGoError(httpResult.Error)) } usedTime := time.Now().UnixMilli() - t1 outHeaders := map[string]string{} for k, v := range httpResult.Response.Header { outHeaders[k] = v[0] } gojsResult := gojs.Map{ "statusCode": httpResult.Response.StatusCode, "status": httpResult.Response.Status, "headers": outHeaders, "usedTime": usedTime, } var result any if action.Method == "WS" { result = otherResult } else { switch action.ResponseFormat { case "json": result = httpResult.Map() case "binary": gojsResult["result"] = httpResult.Bytes() default: gojsResult["result"] = httpResult.String() } } var responseValue goja.Value var resultValue goja.Value if action.SuccessBy != "" || action.FailedMessageBy != "" { responseValue = vm.ToValue(gojsResult) resultValue = vm.ToValue(result) } if action.SuccessBy != "" { fnResult := runFn(action.SuccessBy, vm, "action, request, config, params, headers, data, response, result", actionValue, requestValue, configValue, paramsValue, headersValue, dataValue, responseValue, resultValue) gojsResult["success"] = u.Bool(fnResult) } else { gojsResult["success"] = httpResult.Response.StatusCode == 200 } if gojsResult["success"] == true { gojsResult["failedMessage"] = "" } else { if action.FailedMessageBy != "" { fnResult := runFn(action.FailedMessageBy, vm, "action, request, config, params, headers, data, response, result", actionValue, requestValue, configValue, paramsValue, headersValue, dataValue, responseValue, resultValue) gojsResult["failedMessage"] = u.String(fnResult) } else { gojsResult["failedMessage"] = httpResult.Response.Status } } gojsResult["result"] = result return vm.ToValue(gojsResult) } } func runFn(fnCode string, vm *goja.Runtime, argsParams string, args ...goja.Value) any { fnName := "_CLOUD_FN_" + funcNameExcludesMatcher.ReplaceAllString(fnCode, "_") fnValue := vm.Get(fnName) if fnValue == nil { if _, err := vm.RunString("function " + fnName + "(" + argsParams + "){return " + fnCode + "}"); err != nil { panic(vm.NewGoError(err)) } fnValue = vm.Get(fnName) } if fn, ok := goja.AssertFunction(fnValue); ok { if r, err := fn(nil, args...); err != nil { panic(vm.NewGoError(err)) } else { return r.Export() } } else { panic(vm.NewGoError(errors.New("failed to make function " + fnName))) } } func makeConfig(logger *log.Logger) { if cloudConfigs != nil { return } // 读取所有的配置文件 cloudConfigs = map[string]*CloudConfig{} fixedCloudConfigs = map[string]map[string]*FixedAction{} for _, f := range u.ReadDirN("api") { if strings.HasSuffix(f.Name, ".yml") { apiName := f.Name[:len(f.Name)-4] cloudConf := CloudConfig{} if err := u.LoadX(f.FullName, &cloudConf); err != nil { logger.Error(err.Error()) continue } cloudConfigs[apiName] = &cloudConf } } // 载入补充的配置 config.LoadConfig("cloud", &cloudConfigs) // 根据配置生成gojs对象 for cloudName, cloudConf := range cloudConfigs { // 解密 & 合并配置 if cloudConf.Config == nil { cloudConf.Config = map[string]any{} } decryptConfig(cloudConf.Config) fixedActions := map[string]*FixedAction{} for actionName, apiConf := range cloudConf.Api { // var newConf map[string]any // if v, ok := cloudConf.ApiConfig[actionName]; ok && v != nil { // newConf = *v // } // if newConf == nil { // newConf = map[string]any{} // } // decryptConfig(newConf) // for k, v := range cloudConf.Config { // if _, ok := newConf[k]; !ok { // newConf[k] = v // } // } decryptConfig(apiConf.Config) for k, v := range cloudConf.Config { if _, ok := apiConf.Config[k]; !ok { apiConf.Config[k] = v } } fixedActions[actionName] = &FixedAction{ Action: *apiConf, // Config: newConf, RequiredRequestParams: make([]*RequestParam, 0), NotRequiredRequestParams: make([]*RequestParam, 0), } } for actionName, apiConf := range fixedActions { // 合并请求参数 existsRequestParams := map[string]bool{} for _, param := range apiConf.RequestParams { existsRequestParams[param.Name] = true } for _, param := range cloudConf.RequestParams { if !existsRequestParams[param.Name] { existsRequestParams[param.Name] = true apiConf.RequestParams = append(apiConf.RequestParams, param) } } for _, param := range apiConf.RequestParams { if param.Value == nil { if param.Required { apiConf.RequiredRequestParams = append(apiConf.RequiredRequestParams, param) } else { apiConf.NotRequiredRequestParams = append(apiConf.NotRequiredRequestParams, param) } } else { apiConf.NotRequiredRequestParams = append(apiConf.NotRequiredRequestParams, param) } } fixedActions[actionName].RequestParams = apiConf.RequestParams fixedActions[actionName].RequiredRequestParams = apiConf.RequiredRequestParams fixedActions[actionName].NotRequiredRequestParams = apiConf.NotRequiredRequestParams // 合并请求头 if apiConf.RequestHeaders == nil { apiConf.RequestHeaders = make([]*HeaderParam, 0) } existsRequestHeaders := map[string]bool{} for _, header := range apiConf.RequestHeaders { existsRequestHeaders[header.Name] = true } for _, header := range cloudConf.RequestHeaders { if !existsRequestHeaders[header.Name] { existsRequestHeaders[header.Name] = true apiConf.RequestHeaders = append(apiConf.RequestHeaders, header) } } fixedActions[actionName].RequestHeaders = apiConf.RequestHeaders // 合并响应参数 if apiConf.Response == nil { apiConf.Response = make([]*ResponseParam, 0) } existsResponse := map[string]bool{} for _, resp := range apiConf.Response { existsResponse[resp.Name] = true } for _, resp := range cloudConf.Response { if !existsResponse[resp.Name] { existsResponse[resp.Name] = true apiConf.Response = append(apiConf.Response, resp) } } fixedActions[actionName].Response = apiConf.Response // 处理Sub类型 makeRequestType(cloudName, actionName, fixedActions[actionName].RequestParams) makeResponseType(cloudName, actionName, fixedActions[actionName].Response) // 合并其他 apiConf.Method = strings.ToUpper(apiConf.Method) if apiConf.Method == "" { apiConf.Method = "POST" } apiConf.RequestFormat = strings.ToLower(apiConf.RequestFormat) if apiConf.RequestFormat == "" { apiConf.RequestFormat = cloudConf.RequestFormat } apiConf.ResponseFormat = strings.ToLower(apiConf.ResponseFormat) if apiConf.ResponseFormat == "" { apiConf.ResponseFormat = cloudConf.ResponseFormat } if apiConf.Timeout == 0 { apiConf.Timeout = cloudConf.Timeout } // 生成函数参数 funcParams := make([]string, 0) for _, param := range apiConf.RequiredRequestParams { if len(param.Sub) > 0 { funcParams = append(funcParams, fmt.Sprintf("%s:%s", param.Name, param.Type)) } else { funcParams = append(funcParams, fmt.Sprintf("%s:%s", param.Name, param.Type)) } } if apiConf.RequestFormat == "binary" { funcParams = append(funcParams, "data:any") } if apiConf.Method == "WS" { funcParams = append(funcParams, "callback:(data:any,type:string)=>boolean") } if len(apiConf.NotRequiredRequestParams) > 0 { funcParams = append(funcParams, fmt.Sprintf("options?:%s_%sParams", cloudName, actionName)) } if len(apiConf.RequestHeaders) > 0 { funcParams = append(funcParams, fmt.Sprintf("headers?:%s_%sHeaders", cloudName, actionName)) } apiConf.FuncParams = strings.Join(funcParams, ", ") fixedActions[actionName].FuncParams = apiConf.FuncParams } fixedCloudConfigs[cloudName] = fixedActions } } func makeRequestType(cloudName, actionName string, params []*RequestParam) { for i, param := range params { if len(param.Sub) > 0 { subType := fmt.Sprintf("%s_%sParams%s", cloudName, actionName, param.Name) if param.Type == "Array" { params[i].Type = fmt.Sprintf("Array<%s>", subType) } else { params[i].Type = subType } makeRequestType(cloudName, actionName, param.Sub) } } } func makeResponseType(cloudName, actionName string, params []*ResponseParam) { for i, param := range params { if len(param.Sub) > 0 { subType := fmt.Sprintf("%s_%sResult%s", cloudName, actionName, param.Name) if param.Type == "Array" { params[i].Type = fmt.Sprintf("Array<%s>", subType) } else { params[i].Type = subType } makeResponseType(cloudName, actionName, param.Sub) } } } func decryptConfig(conf map[string]any) { for k, v := range conf { if vStr, ok := v.(string); ok && encryptdMatcher.MatchString(vStr) { conf[k] = confAes.DecryptUrlBase64ToString(vStr) } } }