844 lines
25 KiB
Go
844 lines
25 KiB
Go
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)
|
||
}
|
||
}
|
||
}
|