api/plugin.go
2025-12-01 00:30:00 +08:00

401 lines
9.6 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package plugin
import (
"encoding/base64"
"reflect"
"regexp"
"strings"
"apigo.cc/gojs"
"apigo.cc/gojs/goja"
"github.com/ssgo/config"
"github.com/ssgo/u"
)
const pluginName = "api"
var confAes *u.Aes = u.NewAes([]byte("?GQ$0K0GgLdO=f+~L68PLm$uhKr4'=tV"), []byte("VFs7@sK61cj^f?HZ"))
type Result struct {
StatusCode int
Status string
Headers map[string]string
Data any
}
func init() {
config.LoadConfig(pluginName, &apiConfigs)
for k, v := range apiConfigs {
Config(k, *v, nil)
}
obj := map[string]any{
"config": Config,
"getConfigVersion": GetConfigVersion,
"makeApi": MakeApi,
"registerSigner": RegisterSigner,
"getSignerVersion": GetSignerVersion,
"sortParams": SortParams,
}
// TODO 动态更新配置时无法重新生成obj预处理配置生成Action增加通用的Call方法
for apiName := range apiConfigs {
// cfg := signerConfig[signer]
// o := map[string]any{}
// o["do"] = MakeAction(signer, nil)
// o["get"] = MakeAction(signer, map[string]any{
// "method": "GET",
// })
// o["post"] = MakeAction(signer, map[string]any{
// "method": "POST",
// })
// o["put"] = MakeAction(signer, map[string]any{
// "method": "PUT",
// })
// o["delete"] = MakeAction(signer, map[string]any{
// "method": "DELETE",
// })
// o["head"] = MakeAction(signer, map[string]any{
// "method": "HEAD",
// })
// o["options"] = MakeAction(signer, map[string]any{
// "method": "OPTIONS",
// })
// o["version"] = (*cfg)["_version"]
// actions := map[string]map[string]any{}
// u.Convert((*cfg)["actions"], &actions)
// for k, v := range actions {
// o[k] = MakeAction(signer, v)
// }
obj[apiName] = MakeApi(apiName)
}
tsCode := gojs.MakeTSCode(obj)
mappedObj := gojs.ToMap(obj)
gojs.Register("apigo.cc/gojs/"+pluginName, gojs.Module{
ObjectMaker: func(vm *goja.Runtime) gojs.Map {
return mappedObj
},
TsCode: tsCode,
Desc: pluginName,
JsCode: `
let $MOD$_obj = $MOD$
$MOD$ = new Proxy($MOD$_obj, {
get(target, prop) {
if(['config', 'getConfigVersion', 'registerSigner', 'getSignerVersion', 'sortParams'].indexOf(prop) >= 0) {
return target[prop]
}
let o = target[prop]
let curVer = o && o.version || 0
if(curVer == 0 || curVer != $MOD$_obj.getConfigVersion(prop)){
o = $MOD$_obj.makeApi(prop)
target[prop] = o
}
return o
}
})
`,
})
}
func MakeApi(apiName string) map[string]any {
cfg := apiConfigs[apiName]
o := map[string]any{}
o["do"] = MakeAction(apiName, "", nil)
o["get"] = MakeAction(apiName, "get", map[string]any{
"method": "GET",
})
o["post"] = MakeAction(apiName, "post", map[string]any{
"method": "POST",
})
o["put"] = MakeAction(apiName, "put", map[string]any{
"method": "PUT",
})
o["delete"] = MakeAction(apiName, "delete", map[string]any{
"method": "DELETE",
})
o["head"] = MakeAction(apiName, "head", map[string]any{
"method": "HEAD",
})
o["options"] = MakeAction(apiName, "options", map[string]any{
"method": "OPTIONS",
})
o["version"] = (*cfg)["_version"]
actions := map[string]map[string]any{}
u.Convert((*cfg)["actions"], &actions)
for k, v := range actions {
o[k] = MakeAction(apiName, k, v)
}
return o
}
func MakeAction(apiName, actionName string, actionCfg map[string]any) func(req *Request, vm *goja.Runtime) (*Result, error) {
cfg := &ApiConfig{data: map[string]any{}}
if c := apiConfigs[apiName]; c != nil {
u.Convert(c, cfg.data)
delete(cfg.data, "actions")
}
defaultUrl := cfg.String("url", "")
reqSet := &Request{}
u.Convert(actionCfg, reqSet)
delete(actionCfg, "headers")
delete(actionCfg, "query")
delete(actionCfg, "data")
delete(actionCfg, "form")
delete(actionCfg, "file")
delete(actionCfg, "text")
delete(actionCfg, "binary")
delete(actionCfg, "requestType")
delete(actionCfg, "callback")
delete(actionCfg, "timeout")
u.Convert(actionCfg, cfg.data)
makeConfigVar(cfg.data, reflect.ValueOf(cfg.data))
// 没有在外层配置url或者url中有待处理的变量
if defaultUrl == "" || strings.Contains(defaultUrl, "{{.") {
defaultUrl = cfg.String("url", "")
}
actionFullName := apiName
if actionName != "" {
actionFullName += "." + actionName
}
actionsLock.Lock()
actionConfigs[actionFullName] = cfg
actionsLock.Unlock()
return func(req *Request, vm *goja.Runtime) (*Result, error) {
if req == nil {
req = &Request{}
}
var req1 *Request
if reqSet != nil {
req1v := *reqSet
req1 = &req1v
if req.Url != "" {
req1.Url = req.Url
}
if req.Method != "" {
req1.Method = req.Method
}
if req.Config != nil {
if req1.Config == nil {
req1.Config = map[string]string{}
}
for k, v := range req.Config {
req1.Config[k] = v
}
}
if req.Headers != nil {
if req1.Headers == nil {
req1.Headers = map[string]string{}
}
for k, v := range req.Headers {
req1.Headers[k] = v
}
}
if req.Query != nil {
if req1.Query == nil {
req1.Query = map[string]string{}
}
for k, v := range req.Query {
req1.Query[k] = v
}
}
if req.Data != nil {
if req1.Data == nil {
req1.Data = map[string]any{}
}
for k, v := range req.Data {
req1.Data[k] = v
}
}
if req.Form != nil {
if req1.Form == nil {
req1.Form = map[string]string{}
}
for k, v := range req.Form {
req1.Form[k] = v
}
}
if req.File != nil {
if req1.File == nil {
req1.File = map[string]any{}
}
for k, v := range req.File {
req1.File[k] = v
}
}
if req.Text != "" {
req1.Text = req.Text
}
if req.Binary != nil {
req1.Binary = req.Binary
}
if req.RequestType != "" {
req1.RequestType = req.RequestType
}
if req.ResponseType != "" {
req1.ResponseType = req.ResponseType
}
if req.Callback != nil {
req1.Callback = req.Callback
}
if req.Timeout == 0 {
req1.Timeout = req.Timeout
}
} else {
req1 = req
}
if !strings.Contains(req1.Url, "://") && defaultUrl != "" {
has1 := strings.HasSuffix(defaultUrl, "/")
has2 := strings.HasPrefix(req1.Url, "/")
if !has1 && !has2 {
req1.Url = defaultUrl + "/" + req1.Url
} else if has1 && has2 {
req1.Url = defaultUrl + req1.Url[1:]
} else {
req1.Url = defaultUrl + req1.Url
}
}
data, resp, err := Do(actionFullName, req1, vm)
headers := map[string]string{}
statusCode := 0
status := ""
if resp != nil {
statusCode = resp.StatusCode
status = resp.Status
for k, v := range resp.Header {
headers[k] = v[0]
}
}
return &Result{
Status: status,
StatusCode: statusCode,
Headers: headers,
Data: data,
}, err
}
}
// func Config(cfg map[string]*map[string]any) {
// for k, v := range cfg {
// if u.String((*v)["signer"]) == "" {
// (*v)["signer"] = k
// }
// // 尝试解密配置
// decryptConfig(reflect.ValueOf(v))
// makeConfigVar((*v), reflect.ValueOf(v))
// confLock.Lock()
// signerConfig[k] = v
// confLock.Unlock()
// }
// }
func Config(name string, cfg map[string]any, version *uint64) {
ver := u.Uint64(version)
if ver == 0 {
ver = 1
}
cfg["_version"] = ver
// 尝试解密配置
cfgV := reflect.ValueOf(cfg)
decryptConfig(cfgV)
makeConfigVar(cfg, cfgV)
confLock.Lock()
apiConfigs[name] = &cfg
confLock.Unlock()
}
func GetConfigVersion(name string) uint64 {
confLock.RLock()
defer confLock.RUnlock()
cfg := apiConfigs[name]
if cfg != nil {
return u.Uint64((*cfg)["_version"])
}
return 0
}
func decryptConfig(v reflect.Value) {
v = u.FinalValue(v)
if v.Kind() == reflect.Map {
for _, k := range v.MapKeys() {
v2 := u.FinalValue(v.MapIndex(k))
if v2.Kind() == reflect.String {
if b64, err := base64.URLEncoding.DecodeString(v2.String()); err == nil {
if dec, err := confAes.DecryptBytes(b64); err == nil {
v.SetMapIndex(k, reflect.ValueOf(string(dec)))
}
}
} else if v2.Kind() == reflect.Map || v.Kind() == reflect.Slice {
decryptConfig(v2)
}
}
} else if v.Kind() == reflect.Slice {
for i := 0; i < v.Len(); i++ {
v2 := u.FinalValue(v.Index(i))
if v2.Kind() == reflect.String {
if b64, err := base64.URLEncoding.DecodeString(v2.String()); err == nil {
if dec, err := confAes.DecryptBytes(b64); err == nil {
v.Index(i).Set(reflect.ValueOf(string(dec)))
}
}
} else if v2.Kind() == reflect.Map || v.Kind() == reflect.Slice {
decryptConfig(v2)
}
}
}
}
var varMatcher = regexp.MustCompile(`{{\.([\w\.\-]+)}}`)
var configMatcher = regexp.MustCompile(`{{config\.([\w\.\-]+)}}`)
var fnMatcher = regexp.MustCompile(`{{/(\w+)\s*(.*?)}}`)
func makeConfigVar(cfg map[string]any, v reflect.Value) {
v = u.FinalValue(v)
if v.Kind() == reflect.Map {
for _, k := range v.MapKeys() {
v2 := u.FinalValue(v.MapIndex(k))
if v2.Kind() == reflect.String {
str := v2.String()
if m := varMatcher.FindAllStringSubmatch(str, 100); m != nil {
for _, m1 := range m {
if v1 := getConfigValue(cfg, m1[1]); v1 != "" {
str = strings.ReplaceAll(str, m1[0], v1)
}
}
v.SetMapIndex(k, reflect.ValueOf(str))
}
} else if v2.Kind() == reflect.Map || v.Kind() == reflect.Slice {
makeConfigVar(cfg, v2)
}
}
} else if v.Kind() == reflect.Slice {
for i := 0; i < v.Len(); i++ {
v2 := u.FinalValue(v.Index(i))
if v2.Kind() == reflect.String {
str := v2.String()
if m := varMatcher.FindAllStringSubmatch(str, 100); m != nil {
for _, m1 := range m {
if v1 := getConfigValue(cfg, m1[1]); v1 != "" {
str = strings.ReplaceAll(str, m1[0], v1)
}
}
v.Index(i).Set(reflect.ValueOf(str))
}
} else if v2.Kind() == reflect.Map || v.Kind() == reflect.Slice {
makeConfigVar(cfg, v2)
}
}
}
}