2026-04-01 10:04:52 +08:00
|
|
|
|
package config
|
|
|
|
|
|
|
|
|
|
|
|
import (
|
2026-04-03 09:51:39 +08:00
|
|
|
|
"crypto/aes"
|
|
|
|
|
|
"crypto/cipher"
|
|
|
|
|
|
"crypto/rand"
|
|
|
|
|
|
"encoding/base64"
|
|
|
|
|
|
"errors"
|
2026-04-01 10:04:52 +08:00
|
|
|
|
"os"
|
|
|
|
|
|
"time"
|
|
|
|
|
|
)
|
|
|
|
|
|
|
2026-04-03 09:51:39 +08:00
|
|
|
|
// Encryption key should be provided via environment variable or secure key management
|
|
|
|
|
|
// In production, use a proper key management system (KMS)
|
|
|
|
|
|
// Must be 16, 24, or 32 bytes for AES-128, AES-192, or AES-256
|
|
|
|
|
|
var encryptionKey = []byte(getEnv("PASSWORD_ENCRYPTION_KEY", "default-key-32-bytes-long!!!!!!!"))
|
|
|
|
|
|
|
2026-04-01 10:04:52 +08:00
|
|
|
|
// Config 网关配置
|
|
|
|
|
|
type Config struct {
|
|
|
|
|
|
Server ServerConfig
|
|
|
|
|
|
Database DatabaseConfig
|
|
|
|
|
|
Redis RedisConfig
|
|
|
|
|
|
Router RouterConfig
|
|
|
|
|
|
RateLimit RateLimitConfig
|
|
|
|
|
|
Alert AlertConfig
|
|
|
|
|
|
Providers []ProviderConfig
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ServerConfig 服务配置
|
|
|
|
|
|
type ServerConfig struct {
|
|
|
|
|
|
Host string
|
|
|
|
|
|
Port int
|
|
|
|
|
|
ReadTimeout time.Duration
|
|
|
|
|
|
WriteTimeout time.Duration
|
|
|
|
|
|
IdleTimeout time.Duration
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// DatabaseConfig 数据库配置
|
|
|
|
|
|
type DatabaseConfig struct {
|
2026-04-03 09:51:39 +08:00
|
|
|
|
Host string
|
|
|
|
|
|
Port int
|
|
|
|
|
|
User string
|
|
|
|
|
|
Password string // 兼容旧版本,仍可直接使用明文密码(不推荐)
|
|
|
|
|
|
EncryptedPassword string // 加密后的密码,优先级高于Password字段
|
|
|
|
|
|
Database string
|
|
|
|
|
|
MaxConns int
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// GetPassword 返回解密后的数据库密码
|
|
|
|
|
|
// 优先使用EncryptedPassword,如果为空则返回Password字段(兼容旧版本)
|
|
|
|
|
|
func (c *DatabaseConfig) GetPassword() string {
|
|
|
|
|
|
if c.EncryptedPassword != "" {
|
|
|
|
|
|
decrypted, err := decryptPassword(c.EncryptedPassword)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
// 解密失败时返回原始加密字符串,让后续逻辑处理错误
|
|
|
|
|
|
return c.EncryptedPassword
|
|
|
|
|
|
}
|
|
|
|
|
|
return decrypted
|
|
|
|
|
|
}
|
|
|
|
|
|
return c.Password
|
2026-04-01 10:04:52 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// RedisConfig Redis配置
|
|
|
|
|
|
type RedisConfig struct {
|
2026-04-03 09:51:39 +08:00
|
|
|
|
Host string
|
|
|
|
|
|
Port int
|
|
|
|
|
|
Password string // 兼容旧版本
|
|
|
|
|
|
EncryptedPassword string // 加密后的密码
|
|
|
|
|
|
DB int
|
|
|
|
|
|
PoolSize int
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// GetPassword 返回解密后的Redis密码
|
|
|
|
|
|
func (c *RedisConfig) GetPassword() string {
|
|
|
|
|
|
if c.EncryptedPassword != "" {
|
|
|
|
|
|
decrypted, err := decryptPassword(c.EncryptedPassword)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return c.EncryptedPassword
|
|
|
|
|
|
}
|
|
|
|
|
|
return decrypted
|
|
|
|
|
|
}
|
|
|
|
|
|
return c.Password
|
2026-04-01 10:04:52 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// RouterConfig 路由配置
|
|
|
|
|
|
type RouterConfig struct {
|
|
|
|
|
|
Strategy string // "latency", "cost", "availability", "weighted"
|
|
|
|
|
|
Timeout time.Duration
|
|
|
|
|
|
MaxRetries int
|
|
|
|
|
|
RetryDelay time.Duration
|
|
|
|
|
|
HealthCheckInterval time.Duration
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// RateLimitConfig 限流配置
|
|
|
|
|
|
type RateLimitConfig struct {
|
|
|
|
|
|
Enabled bool
|
|
|
|
|
|
Algorithm string // "token_bucket", "sliding_window", "fixed_window"
|
|
|
|
|
|
DefaultRPM int // 请求数/分钟
|
|
|
|
|
|
DefaultTPM int // Token数/分钟
|
|
|
|
|
|
BurstMultiplier float64
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// AlertConfig 告警配置
|
|
|
|
|
|
type AlertConfig struct {
|
|
|
|
|
|
Enabled bool
|
|
|
|
|
|
Email EmailConfig
|
|
|
|
|
|
DingTalk DingTalkConfig
|
|
|
|
|
|
Feishu FeishuConfig
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// EmailConfig 邮件配置
|
|
|
|
|
|
type EmailConfig struct {
|
|
|
|
|
|
Enabled bool
|
|
|
|
|
|
Host string
|
|
|
|
|
|
Port int
|
|
|
|
|
|
Username string
|
|
|
|
|
|
Password string
|
|
|
|
|
|
From string
|
|
|
|
|
|
To []string
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// DingTalkConfig 钉钉配置
|
|
|
|
|
|
type DingTalkConfig struct {
|
|
|
|
|
|
Enabled bool
|
|
|
|
|
|
WebHook string
|
|
|
|
|
|
Secret string
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// FeishuConfig 飞书配置
|
|
|
|
|
|
type FeishuConfig struct {
|
|
|
|
|
|
Enabled bool
|
|
|
|
|
|
WebHook string
|
|
|
|
|
|
Secret string
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ProviderConfig Provider配置
|
|
|
|
|
|
type ProviderConfig struct {
|
|
|
|
|
|
Name string
|
|
|
|
|
|
Type string // "openai", "anthropic", "google", "custom"
|
|
|
|
|
|
BaseURL string
|
|
|
|
|
|
APIKey string
|
|
|
|
|
|
Models []string
|
|
|
|
|
|
Priority int
|
|
|
|
|
|
Weight float64
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// LoadConfig 加载配置
|
|
|
|
|
|
func LoadConfig(path string) (*Config, error) {
|
|
|
|
|
|
// 简化实现,实际应使用viper或类似库
|
|
|
|
|
|
cfg := &Config{
|
|
|
|
|
|
Server: ServerConfig{
|
|
|
|
|
|
Host: getEnv("GATEWAY_HOST", "0.0.0.0"),
|
|
|
|
|
|
Port: 8080,
|
|
|
|
|
|
ReadTimeout: 30 * time.Second,
|
|
|
|
|
|
WriteTimeout: 30 * time.Second,
|
|
|
|
|
|
IdleTimeout: 120 * time.Second,
|
|
|
|
|
|
},
|
|
|
|
|
|
Router: RouterConfig{
|
|
|
|
|
|
Strategy: "latency",
|
|
|
|
|
|
Timeout: 30 * time.Second,
|
|
|
|
|
|
MaxRetries: 3,
|
|
|
|
|
|
RetryDelay: 1 * time.Second,
|
|
|
|
|
|
HealthCheckInterval: 10 * time.Second,
|
|
|
|
|
|
},
|
|
|
|
|
|
RateLimit: RateLimitConfig{
|
|
|
|
|
|
Enabled: true,
|
|
|
|
|
|
Algorithm: "token_bucket",
|
|
|
|
|
|
DefaultRPM: 60,
|
|
|
|
|
|
DefaultTPM: 60000,
|
|
|
|
|
|
BurstMultiplier: 1.5,
|
|
|
|
|
|
},
|
|
|
|
|
|
Alert: AlertConfig{
|
|
|
|
|
|
Enabled: true,
|
|
|
|
|
|
Email: EmailConfig{
|
|
|
|
|
|
Enabled: false,
|
|
|
|
|
|
Host: getEnv("SMTP_HOST", "smtp.example.com"),
|
|
|
|
|
|
Port: 587,
|
|
|
|
|
|
},
|
|
|
|
|
|
DingTalk: DingTalkConfig{
|
|
|
|
|
|
Enabled: getEnv("DINGTALK_ENABLED", "false") == "true",
|
|
|
|
|
|
WebHook: getEnv("DINGTALK_WEBHOOK", ""),
|
|
|
|
|
|
Secret: getEnv("DINGTALK_SECRET", ""),
|
|
|
|
|
|
},
|
|
|
|
|
|
Feishu: FeishuConfig{
|
|
|
|
|
|
Enabled: getEnv("FEISHU_ENABLED", "false") == "true",
|
|
|
|
|
|
WebHook: getEnv("FEISHU_WEBHOOK", ""),
|
|
|
|
|
|
Secret: getEnv("FEISHU_SECRET", ""),
|
|
|
|
|
|
},
|
|
|
|
|
|
},
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return cfg, nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func getEnv(key, defaultValue string) string {
|
|
|
|
|
|
if value := os.Getenv(key); value != "" {
|
|
|
|
|
|
return value
|
|
|
|
|
|
}
|
|
|
|
|
|
return defaultValue
|
|
|
|
|
|
}
|
2026-04-03 09:51:39 +08:00
|
|
|
|
|
|
|
|
|
|
// encryptPassword 使用AES-GCM加密密码
|
|
|
|
|
|
func encryptPassword(plaintext string) (string, error) {
|
|
|
|
|
|
if plaintext == "" {
|
|
|
|
|
|
return "", nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
block, err := aes.NewCipher(encryptionKey)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return "", err
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
gcm, err := cipher.NewGCM(block)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return "", err
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
nonce := make([]byte, gcm.NonceSize())
|
|
|
|
|
|
if _, err := rand.Read(nonce); err != nil {
|
|
|
|
|
|
return "", err
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
ciphertext := gcm.Seal(nonce, nonce, []byte(plaintext), nil)
|
|
|
|
|
|
return base64.StdEncoding.EncodeToString(ciphertext), nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// decryptPassword 解密密码
|
|
|
|
|
|
func decryptPassword(encrypted string) (string, error) {
|
|
|
|
|
|
if encrypted == "" {
|
|
|
|
|
|
return "", nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 检查是否是旧格式(未加密的明文)
|
|
|
|
|
|
if len(encrypted) < 4 || encrypted[:4] != "enc:" {
|
|
|
|
|
|
// 尝试作为新格式解密
|
|
|
|
|
|
ciphertext, err := base64.StdEncoding.DecodeString(encrypted)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
// 如果不是有效的base64,可能是旧格式明文,直接返回
|
|
|
|
|
|
return encrypted, nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
block, err := aes.NewCipher(encryptionKey)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return "", err
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
gcm, err := cipher.NewGCM(block)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return "", err
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
nonceSize := gcm.NonceSize()
|
|
|
|
|
|
if len(ciphertext) < nonceSize {
|
|
|
|
|
|
return "", errors.New("ciphertext too short")
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
nonce, ciphertext := ciphertext[:nonceSize], ciphertext[nonceSize:]
|
|
|
|
|
|
plaintext, err := gcm.Open(nil, nonce, ciphertext, nil)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return "", err
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return string(plaintext), nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 旧格式:直接返回"enc:"后的部分
|
|
|
|
|
|
return encrypted[4:], nil
|
|
|
|
|
|
}
|