193 lines
3.9 KiB
Go
193 lines
3.9 KiB
Go
|
|
package api
|
|||
|
|
|
|||
|
|
import (
|
|||
|
|
"reflect"
|
|||
|
|
"strings"
|
|||
|
|
"sync"
|
|||
|
|
|
|||
|
|
"apigo.cc/go/cast"
|
|||
|
|
"apigo.cc/go/config"
|
|||
|
|
"apigo.cc/go/crypto"
|
|||
|
|
"apigo.cc/go/encoding"
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
var confAes, _ = crypto.NewAESGCMAndEraseKey([]byte("?GQ$0K0GgLdO=f+~L68PLm$uhKr4'=tV"), []byte("VFs7@sK61cj^f?HZ"))
|
|||
|
|
var keysOnce = sync.Once{}
|
|||
|
|
var configMutex sync.RWMutex
|
|||
|
|
|
|||
|
|
// GlobalConfigs 存储整棵配置树
|
|||
|
|
var GlobalConfigs = map[string]any{}
|
|||
|
|
|
|||
|
|
// SetEncryptKeys 允许设置自定义的配置加解密密钥
|
|||
|
|
func SetEncryptKeys(key, iv []byte) {
|
|||
|
|
keysOnce.Do(func() {
|
|||
|
|
if confAes != nil {
|
|||
|
|
confAes.Close()
|
|||
|
|
}
|
|||
|
|
confAes, _ = crypto.NewAESGCMAndEraseKey(key, iv)
|
|||
|
|
})
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Load 加载指定的配置文件并合并到 GlobalConfigs
|
|||
|
|
func Load(name string) error {
|
|||
|
|
if name == "" {
|
|||
|
|
name = "api"
|
|||
|
|
}
|
|||
|
|
var conf map[string]any
|
|||
|
|
err := config.Load(&conf, name)
|
|||
|
|
if err != nil {
|
|||
|
|
return err
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
configMutex.Lock()
|
|||
|
|
defer configMutex.Unlock()
|
|||
|
|
|
|||
|
|
// 合并到全局树
|
|||
|
|
MergeMap(GlobalConfigs, conf)
|
|||
|
|
return nil
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// GetActionConfig 获取某个动作经过层级合并后的完整配置
|
|||
|
|
func GetActionConfig(actionName string) map[string]any {
|
|||
|
|
configMutex.RLock()
|
|||
|
|
defer configMutex.RUnlock()
|
|||
|
|
|
|||
|
|
parts := strings.Split(actionName, ".")
|
|||
|
|
res := map[string]any{}
|
|||
|
|
|
|||
|
|
// 1. 获取 api 根节点
|
|||
|
|
curr, ok := GlobalConfigs["api"].(map[string]any)
|
|||
|
|
if !ok {
|
|||
|
|
return res
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 2. 逐级导航并合并
|
|||
|
|
for _, part := range parts {
|
|||
|
|
next, ok := curr[part].(map[string]any)
|
|||
|
|
if !ok {
|
|||
|
|
// 尝试在 actions 目录下寻找 (可选约定)
|
|||
|
|
if actions, ok := curr["actions"].(map[string]any); ok {
|
|||
|
|
next, ok = actions[part].(map[string]any)
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if next != nil {
|
|||
|
|
MergeMap(res, next)
|
|||
|
|
curr = next
|
|||
|
|
} else {
|
|||
|
|
break
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
decryptMap(res)
|
|||
|
|
return res
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// fill 注入配置到 Action,非破坏性,仅注入零值
|
|||
|
|
func fill(action any, actionConfig map[string]any) {
|
|||
|
|
if action == nil || actionConfig == nil {
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
v := reflect.ValueOf(action)
|
|||
|
|
for v.Kind() == reflect.Ptr {
|
|||
|
|
v = v.Elem()
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if v.Kind() == reflect.Struct {
|
|||
|
|
t := v.Type()
|
|||
|
|
for i := 0; i < v.NumField(); i++ {
|
|||
|
|
f := v.Field(i)
|
|||
|
|
if !f.CanSet() || !f.IsZero() {
|
|||
|
|
continue
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
fieldName := t.Field(i).Name
|
|||
|
|
if val, ok := findConfigValue(actionConfig, fieldName); ok {
|
|||
|
|
cast.Convert(f.Addr().Interface(), val)
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
} else if v.Kind() == reflect.Map {
|
|||
|
|
for _, key := range v.MapKeys() {
|
|||
|
|
kv := v.MapIndex(key)
|
|||
|
|
if isZero(kv) {
|
|||
|
|
if val, ok := findConfigValue(actionConfig, cast.String(key.Interface())); ok {
|
|||
|
|
v.SetMapIndex(key, reflect.ValueOf(val))
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func findConfigValue(m map[string]any, key string) (any, bool) {
|
|||
|
|
if v, ok := m[key]; ok {
|
|||
|
|
return v, true
|
|||
|
|
}
|
|||
|
|
lk := strings.ToLower(key)
|
|||
|
|
for k, v := range m {
|
|||
|
|
if strings.ToLower(k) == lk {
|
|||
|
|
return v, true
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
return nil, false
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func isZero(v reflect.Value) bool {
|
|||
|
|
if !v.IsValid() {
|
|||
|
|
return true
|
|||
|
|
}
|
|||
|
|
switch v.Kind() {
|
|||
|
|
case reflect.Interface, reflect.Ptr, reflect.Map, reflect.Slice:
|
|||
|
|
return v.IsNil()
|
|||
|
|
default:
|
|||
|
|
return v.IsZero()
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// MergeMap 深度合并两个 map
|
|||
|
|
func MergeMap(dst, src map[string]any) {
|
|||
|
|
for k, v := range src {
|
|||
|
|
if k == "actions" || k == "raw" {
|
|||
|
|
continue
|
|||
|
|
}
|
|||
|
|
if v == nil {
|
|||
|
|
continue
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if srcMap, ok := v.(map[string]any); ok {
|
|||
|
|
dstVal, ok := dst[k]
|
|||
|
|
var dstMap map[string]any
|
|||
|
|
if ok {
|
|||
|
|
dstMap, _ = dstVal.(map[string]any)
|
|||
|
|
}
|
|||
|
|
if dstMap == nil {
|
|||
|
|
dstMap = make(map[string]any)
|
|||
|
|
dst[k] = dstMap
|
|||
|
|
}
|
|||
|
|
MergeMap(dstMap, srcMap)
|
|||
|
|
} else {
|
|||
|
|
dst[k] = v
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func decryptMap(m map[string]any) {
|
|||
|
|
for k, v := range m {
|
|||
|
|
if s, ok := v.(string); ok {
|
|||
|
|
if b64, err := encoding.UnUrlBase64FromString(s); err == nil {
|
|||
|
|
if dec, err := confAes.DecryptBytes(b64); err == nil {
|
|||
|
|
m[k] = string(dec)
|
|||
|
|
continue
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
if b64, err := encoding.UnBase64FromString(s); err == nil {
|
|||
|
|
if dec, err := confAes.DecryptBytes(b64); err == nil {
|
|||
|
|
m[k] = string(dec)
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
} else if subMap, ok := v.(map[string]any); ok {
|
|||
|
|
decryptMap(subMap)
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|