api/config.go
2026-06-10 10:03:57 +08:00

270 lines
6.3 KiB
Go
Raw 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 api
import (
"reflect"
"strings"
"sync"
"apigo.cc/go/cast"
"apigo.cc/go/config"
"apigo.cc/go/crypto"
"apigo.cc/go/encoding"
"apigo.cc/go/safe"
)
var confAES *crypto.Symmetric
func init() {
crypto.OnSetDefaultAES(func(aes *crypto.Symmetric) {
confAES = aes
})
}
var configMutex sync.RWMutex
// GlobalConfigs 存储整棵配置树
var GlobalConfigs = map[string]any{}
// SetEncryptKeys 允许设置自定义的配置加解密密钥
func SetEncryptKeys(key, iv []byte) {
crypto.SetDefaultAES(key, iv)
}
// SetConfig 允许在内存中动态添加或覆盖配置,适用于从数据库或知识库加载配置的场景。
// 它会自动扫描 map 中的字符串,识别并解密以 "**" 开头的加密内容并转换为 SafeBuf。
func SetConfig(name string, conf map[string]any) {
configMutex.Lock()
defer configMutex.Unlock()
// 1. 扫描并自动解密敏感数据
decryptMapWithPrefix(conf)
// 注意:解密出的 SafeBufs 是常驻内存的配置,由 GlobalConfigs 持有,不需要立即关闭
// 2. 合并到全局树
if GlobalConfigs[name] == nil {
GlobalConfigs[name] = conf
} else if dst, ok := GlobalConfigs[name].(map[string]any); ok {
MergeMap(dst, conf)
}
}
// AddConfig 是 SetConfig 的兼容性别名
func AddConfig(name string, conf map[string]any) { SetConfig(name, conf) }
// 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
}
SetConfig(name, conf)
return nil
}
// GetActionConfig 获取某个动作经过层级合并后的完整配置,返回配置图和需要手动关闭的 SafeBuf 列表
func GetActionConfig(actionName string) (map[string]any, []*safe.SafeBuf) {
configMutex.RLock()
defer configMutex.RUnlock()
parts := strings.Split(actionName, ".")
res := map[string]any{}
// 1. 获取 api 根节点
curr, ok := GlobalConfigs["api"].(map[string]any)
if !ok {
// 尝试直接从根开始找
curr = GlobalConfigs
}
// 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
}
}
// 这里的解密主要是为了处理文件中加载的 ENC() 格式 (兼容旧版)
safeBufs := decryptMap(res)
return res, safeBufs
}
// fill 注入配置到 Action非破坏性仅注入零值
func fill(action any, actionConfig map[string]any) {
if action == nil || actionConfig == nil {
return
}
if ga, ok := action.(*GenericAction); ok {
// 对于通用动作,将配置合并到 payload map 中(仅合并 payload 不存在的 key
for k, v := range actionConfig {
if k == "url" || k == "method" || k == "signer" || k == "format" || k == "headers" || k == "timeout" {
continue
}
if _, exists := ga.payload[k]; !exists {
ga.payload[k] = v
}
}
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 {
// 如果目标是 string 但值是 SafeBuf默认不注入以防泄露除非手动处理
if f.Kind() == reflect.String {
if _, isSafe := val.(*safe.SafeBuf); isSafe {
continue
}
}
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 decryptMapWithPrefix(m map[string]any) []*safe.SafeBuf {
var safeBufs []*safe.SafeBuf
for k, v := range m {
if s, ok := v.(string); ok && strings.HasPrefix(s, "**") {
raw := s[2:]
var b64 []byte
var err error
if b64, err = encoding.UnURLBase64(raw); err != nil {
b64, err = encoding.UnBase64(raw)
}
if err == nil && len(b64) > 0 {
if dec, err := confAES.DecryptBytes(b64); err == nil {
sb := safe.NewSafeBufAndErase(dec)
m[k] = sb
safeBufs = append(safeBufs, sb)
continue
}
}
} else if subMap, ok := v.(map[string]any); ok {
safeBufs = append(safeBufs, decryptMapWithPrefix(subMap)...)
}
}
return safeBufs
}
func decryptMap(m map[string]any) []*safe.SafeBuf {
var safeBufs []*safe.SafeBuf
for k, v := range m {
if s, ok := v.(string); ok {
var b64 []byte
var err error
// 兼容旧版 ENC(...) 格式扫描
inner := s
if strings.HasPrefix(s, "ENC(") && strings.HasSuffix(s, ")") {
inner = s[4 : len(s)-1]
}
if b64, err = encoding.UnURLBase64(inner); err != nil {
b64, err = encoding.UnBase64(inner)
}
if err == nil && len(b64) > 0 {
if dec, err := confAES.DecryptBytes(b64); err == nil {
sb := safe.NewSafeBufAndErase(dec)
m[k] = sb
safeBufs = append(safeBufs, sb)
continue
}
}
} else if subMap, ok := v.(map[string]any); ok {
safeBufs = append(safeBufs, decryptMap(subMap)...)
}
}
return safeBufs
}