Bladeren bron

feat: 完成阶段一: 基础设施

hank9999 3 maanden geleden
bovenliggende
commit
2950f0ba55

+ 15 - 12
cmd/server/main.go

@@ -14,7 +14,6 @@ import (
 	"github.com/ding113/claude-code-hub/internal/pkg/logger"
 	"github.com/ding113/claude-code-hub/internal/pkg/validator"
 	"github.com/gin-gonic/gin"
-	"github.com/redis/go-redis/v9"
 	"github.com/uptrace/bun"
 )
 
@@ -96,7 +95,7 @@ func main() {
 }
 
 // setupRouter 设置路由
-func setupRouter(db *bun.DB, rdb *redis.Client) *gin.Engine {
+func setupRouter(db *bun.DB, rdb *database.RedisClient) *gin.Engine {
 	router := gin.New()
 
 	// 添加中间件
@@ -164,7 +163,7 @@ func requestLogger() gin.HandlerFunc {
 }
 
 // healthCheck 健康检查处理器
-func healthCheck(db *bun.DB, rdb *redis.Client) gin.HandlerFunc {
+func healthCheck(db *bun.DB, rdb *database.RedisClient) gin.HandlerFunc {
 	return func(c *gin.Context) {
 		ctx := c.Request.Context()
 
@@ -179,20 +178,24 @@ func healthCheck(db *bun.DB, rdb *redis.Client) gin.HandlerFunc {
 		}
 
 		// 检查 Redis 连接
-		if err := rdb.Ping(ctx).Err(); err != nil {
-			c.JSON(http.StatusServiceUnavailable, gin.H{
-				"status":   "unhealthy",
-				"redis":    "disconnected",
-				"database": "connected",
-				"error":    err.Error(),
-			})
-			return
+		redisStatus := "disabled"
+		if rdb != nil {
+			if err := rdb.Ping(ctx).Err(); err != nil {
+				c.JSON(http.StatusServiceUnavailable, gin.H{
+					"status":   "unhealthy",
+					"redis":    "disconnected",
+					"database": "connected",
+					"error":    err.Error(),
+				})
+				return
+			}
+			redisStatus = "connected"
 		}
 
 		c.JSON(http.StatusOK, gin.H{
 			"status":   "healthy",
 			"database": "connected",
-			"redis":    "connected",
+			"redis":    redisStatus,
 		})
 	}
 }

+ 214 - 21
internal/config/config.go

@@ -4,11 +4,30 @@ import "time"
 
 // Config 应用配置
 type Config struct {
-	Server   ServerConfig   `mapstructure:"server"`
-	Database DatabaseConfig `mapstructure:"database"`
-	Redis    RedisConfig    `mapstructure:"redis"`
-	Log      LogConfig      `mapstructure:"log"`
-	Auth     AuthConfig     `mapstructure:"auth"`
+	// Env: 环境模式 (development, production, test)
+	Env string `mapstructure:"env"`
+
+	Server         ServerConfig         `mapstructure:"server"`
+	Database       DatabaseConfig       `mapstructure:"database"`
+	Redis          RedisConfig          `mapstructure:"redis"`
+	Log            LogConfig            `mapstructure:"log"`
+	Auth           AuthConfig           `mapstructure:"auth"`
+	MessageRequest MessageRequestConfig `mapstructure:"message_request"`
+	Features       FeaturesConfig       `mapstructure:"features"`
+	Proxy          ProxyConfig          `mapstructure:"proxy"`
+	Session        SessionConfig        `mapstructure:"session"`
+	SmartProbing   SmartProbingConfig   `mapstructure:"smart_probing"`
+	APITest        APITestConfig        `mapstructure:"api_test"`
+	App            AppConfig            `mapstructure:"app"`
+
+	// Timezone: 时区,默认 Asia/Shanghai
+	Timezone string `mapstructure:"timezone"`
+
+	// DebugMode: 调试模式
+	DebugMode bool `mapstructure:"debug_mode"`
+
+	// AutoMigrate: 自动迁移数据库
+	AutoMigrate bool `mapstructure:"auto_migrate"`
 }
 
 // ServerConfig 服务器配置
@@ -21,38 +40,212 @@ type ServerConfig struct {
 }
 
 // DatabaseConfig 数据库配置
+// 支持两种配置方式:
+// 1. DSN 连接字符串(优先)
+// 2. 分离的配置字段(Host, Port, User, Password, DBName, SSLMode)
 type DatabaseConfig struct {
-	Host         string        `mapstructure:"host"`
-	Port         int           `mapstructure:"port"`
-	User         string        `mapstructure:"user"`
-	Password     string        `mapstructure:"password"`
-	DBName       string        `mapstructure:"dbname"`
-	SSLMode      string        `mapstructure:"sslmode"`
-	MaxOpenConns int           `mapstructure:"max_open_conns"`
-	MaxIdleConns int           `mapstructure:"max_idle_conns"`
-	ConnLifetime time.Duration `mapstructure:"conn_lifetime"`
+	// DSN 连接字符串,格式:postgres://user:password@host:port/dbname?sslmode=disable
+	// 如果设置了 DSN,则忽略 Host, Port, User, Password, DBName, SSLMode 字段
+	DSN string `mapstructure:"dsn"`
+
+	// 分离的配置字段(当 DSN 为空时使用)
+	Host     string `mapstructure:"host"`
+	Port     int    `mapstructure:"port"`
+	User     string `mapstructure:"user"`
+	Password string `mapstructure:"password"`
+	DBName   string `mapstructure:"dbname"`
+	SSLMode  string `mapstructure:"sslmode"`
+
+	// 连接池配置
+	// PoolMax: 最大打开连接数
+	// - 多副本部署(k8s)需要结合数据库 max_connections 分摊配置
+	// - 范围: 1-200
+	PoolMax int `mapstructure:"pool_max"`
+
+	// MaxIdleConns: 最大空闲连接数
+	MaxIdleConns int `mapstructure:"max_idle_conns"`
+
+	// IdleTimeout: 空闲连接回收时间(秒)
+	// - 范围: 0-3600
+	IdleTimeout time.Duration `mapstructure:"idle_timeout"`
+
+	// ConnectTimeout: 建立连接超时时间(秒)
+	// - 范围: 1-120
+	ConnectTimeout time.Duration `mapstructure:"connect_timeout"`
+
+	// ConnMaxLifetime: 连接最大生命周期
+	ConnMaxLifetime time.Duration `mapstructure:"conn_max_lifetime"`
 }
 
 // RedisConfig Redis配置
+// 支持两种配置方式:
+// 1. URL 连接字符串(优先)- 支持 redis:// 和 rediss:// (TLS) 协议
+// 2. 分离的配置字段(Host, Port, Password, DB)
 type RedisConfig struct {
-	Host         string        `mapstructure:"host"`
-	Port         int           `mapstructure:"port"`
-	Password     string        `mapstructure:"password"`
-	DB           int           `mapstructure:"db"`
+	// URL 连接方式 (优先级高于 Host:Port)
+	// 支持 redis:// 和 rediss:// (TLS) 协议
+	URL string `mapstructure:"url"`
+
+	// Host:Port 连接方式 (当 URL 为空时使用)
+	Host     string `mapstructure:"host"`
+	Port     int    `mapstructure:"port"`
+	Password string `mapstructure:"password"`
+	DB       int    `mapstructure:"db"`
+
+	// 连接池配置
 	PoolSize     int           `mapstructure:"pool_size"`
 	MinIdleConns int           `mapstructure:"min_idle_conns"`
 	DialTimeout  time.Duration `mapstructure:"dial_timeout"`
 	ReadTimeout  time.Duration `mapstructure:"read_timeout"`
 	WriteTimeout time.Duration `mapstructure:"write_timeout"`
+
+	// TLS 配置
+	// TLSRejectUnauthorized: 是否验证 TLS 证书
+	// - 默认 true,生产环境建议保持 true
+	// - 设置为 false 可跳过证书验证(仅用于测试环境)
+	TLSRejectUnauthorized bool `mapstructure:"tls_reject_unauthorized"`
+
+	// 重试配置
+	// MaxRetries: 每个请求的最大重试次数,默认 3
+	MaxRetries int `mapstructure:"max_retries"`
+	// MinRetryBackoff: 最小重试间隔,默认 200ms
+	MinRetryBackoff time.Duration `mapstructure:"min_retry_backoff"`
+	// MaxRetryBackoff: 最大重试间隔,默认 2s
+	MaxRetryBackoff time.Duration `mapstructure:"max_retry_backoff"`
+
+	// 功能开关
+	// Enabled: 是否启用 Redis
+	Enabled bool `mapstructure:"enabled"`
 }
 
 // LogConfig 日志配置
 type LogConfig struct {
-	Level  string `mapstructure:"level"`
-	Format string `mapstructure:"format"` // json, text
+	// Level: 日志级别 (fatal, error, warn, info, debug, trace)
+	Level string `mapstructure:"level"`
+	// Format: 日志格式 (json, text)
+	Format string `mapstructure:"format"`
 }
 
 // AuthConfig 认证配置
 type AuthConfig struct {
-	AdminAPIKey string `mapstructure:"admin_api_key"`
+	// AdminToken: 管理员令牌
+	// 用于访问管理 API
+	AdminToken string `mapstructure:"admin_token"`
+}
+
+// MessageRequestConfig 消息请求配置
+type MessageRequestConfig struct {
+	// WriteMode: 写入模式
+	// - sync: 同步写入(兼容旧行为,但高并发下会增加请求尾部阻塞)
+	// - async: 异步批量写入(默认,降低 DB 写放大与连接占用)
+	WriteMode string `mapstructure:"write_mode"`
+
+	// AsyncFlushIntervalMs: 异步批量写入刷新间隔(毫秒)
+	// - 范围: 10-60000
+	AsyncFlushIntervalMs int `mapstructure:"async_flush_interval_ms"`
+
+	// AsyncBatchSize: 异步批量写入批量大小
+	// - 范围: 1-2000
+	AsyncBatchSize int `mapstructure:"async_batch_size"`
+
+	// AsyncMaxPending: 异步批量写入最大待处理数
+	// - 范围: 100-200000
+	AsyncMaxPending int `mapstructure:"async_max_pending"`
+}
+
+// FeaturesConfig 功能开关配置
+type FeaturesConfig struct {
+	// EnableRateLimit: 启用限流
+	EnableRateLimit bool `mapstructure:"enable_rate_limit"`
+
+	// EnableSecureCookies: 启用安全Cookie
+	EnableSecureCookies bool `mapstructure:"enable_secure_cookies"`
+
+	// EnableMultiProviderTypes: 启用多供应商类型
+	EnableMultiProviderTypes bool `mapstructure:"enable_multi_provider_types"`
+
+	// EnableCircuitBreakerOnNetworkErrors: 网络错误时启用熔断器
+	EnableCircuitBreakerOnNetworkErrors bool `mapstructure:"enable_circuit_breaker_on_network_errors"`
+
+	// EnableProviderCache: 启用供应商缓存
+	// - true (默认): 启用进程级缓存,30s TTL,提升供应商查询性能
+	// - false: 禁用缓存,每次请求直接查询数据库
+	EnableProviderCache bool `mapstructure:"enable_provider_cache"`
+
+	// EnableSmartProbing: 启用智能探测
+	// - false (默认): 禁用智能探测
+	// - true: 当熔断器处于 OPEN 状态时,定期探测供应商以实现更快恢复
+	EnableSmartProbing bool `mapstructure:"enable_smart_probing"`
+}
+
+// ProxyConfig 代理配置
+type ProxyConfig struct {
+	// MaxRetryAttemptsDefault: 默认最大重试次数
+	// - 范围: 1-10
+	MaxRetryAttemptsDefault int `mapstructure:"max_retry_attempts_default"`
+
+	// FetchBodyTimeout: 请求/响应体传输超时
+	// - 默认 600 秒
+	FetchBodyTimeout time.Duration `mapstructure:"fetch_body_timeout"`
+
+	// FetchHeadersTimeout: 响应头接收超时
+	// - 默认 600 秒
+	FetchHeadersTimeout time.Duration `mapstructure:"fetch_headers_timeout"`
+
+	// FetchConnectTimeout: TCP 连接建立超时
+	// - 默认 30 秒
+	FetchConnectTimeout time.Duration `mapstructure:"fetch_connect_timeout"`
+}
+
+// SessionConfig 会话配置
+type SessionConfig struct {
+	// TTL: 会话 TTL(秒)
+	TTL int `mapstructure:"ttl"`
+
+	// StoreSessionMessages: 是否存储请求 messages 到 Redis
+	// - false (默认): 不存储
+	// - true: 存储(用于实时监控页面查看详情,会增加 Redis 内存使用)
+	StoreSessionMessages bool `mapstructure:"store_session_messages"`
+}
+
+// SmartProbingConfig 智能探测配置
+type SmartProbingConfig struct {
+	// IntervalMs: 探测周期间隔(毫秒)
+	// - 默认 30000(30秒)
+	IntervalMs int `mapstructure:"interval_ms"`
+
+	// TimeoutMs: 单次探测超时时间(毫秒)
+	// - 默认 5000(5秒)
+	TimeoutMs int `mapstructure:"timeout_ms"`
+}
+
+// APITestConfig API 测试配置
+type APITestConfig struct {
+	// TimeoutMs: API 测试请求超时时间(毫秒)
+	// - 范围: 5000-120000
+	// - 默认 15000
+	TimeoutMs int `mapstructure:"timeout_ms"`
+}
+
+// AppConfig 应用配置
+type AppConfig struct {
+	// URL: 应用访问地址
+	// - 留空自动检测
+	// - 生产环境建议显式配置,如 https://your-domain.com
+	URL string `mapstructure:"url"`
+}
+
+// IsDevelopment 检查是否为开发环境
+func (c *Config) IsDevelopment() bool {
+	return c.Env == "development"
+}
+
+// IsProduction 检查是否为生产环境
+func (c *Config) IsProduction() bool {
+	return c.Env == "production"
+}
+
+// IsTest 检查是否为测试环境
+func (c *Config) IsTest() bool {
+	return c.Env == "test"
 }

+ 196 - 15
internal/config/loader.go

@@ -43,30 +43,52 @@ func Load() (*Config, error) {
 		return nil, fmt.Errorf("failed to unmarshal config: %w", err)
 	}
 
+	// 验证配置
+	if err := validate(&cfg); err != nil {
+		return nil, fmt.Errorf("config validation failed: %w", err)
+	}
+
 	return &cfg, nil
 }
 
 // setDefaults 设置默认值
+// 与 Node.js 版本的 env.schema.ts 保持一致
 func setDefaults(v *viper.Viper) {
+	// 环境模式
+	v.SetDefault("env", "development")
+
+	// 时区
+	v.SetDefault("timezone", "Asia/Shanghai")
+
+	// 调试模式
+	v.SetDefault("debug_mode", false)
+
+	// 自动迁移
+	v.SetDefault("auto_migrate", true)
+
 	// Server defaults
-	v.SetDefault("server.port", 8080)
+	v.SetDefault("server.port", 23000) // 与 Node.js 版本保持一致
 	v.SetDefault("server.host", "0.0.0.0")
 	v.SetDefault("server.read_timeout", 30*time.Second)
 	v.SetDefault("server.write_timeout", 120*time.Second) // SSE 需要较长超时
 	v.SetDefault("server.shutdown_timeout", 30*time.Second)
 
 	// Database defaults
+	v.SetDefault("database.dsn", "")
 	v.SetDefault("database.host", "localhost")
 	v.SetDefault("database.port", 5432)
 	v.SetDefault("database.user", "postgres")
 	v.SetDefault("database.password", "")
 	v.SetDefault("database.dbname", "claude_code_hub")
 	v.SetDefault("database.sslmode", "disable")
-	v.SetDefault("database.max_open_conns", 20)
-	v.SetDefault("database.max_idle_conns", 5)
-	v.SetDefault("database.conn_lifetime", 30*time.Minute)
+	v.SetDefault("database.pool_max", 20)                    // 与 Node.js 版本一致,范围 1-200
+	v.SetDefault("database.max_idle_conns", 5)               // 最大空闲连接数
+	v.SetDefault("database.idle_timeout", 20*time.Second)    // 与 Node.js 版本一致,范围 0-3600
+	v.SetDefault("database.connect_timeout", 10*time.Second) // 与 Node.js 版本一致,范围 1-120
+	v.SetDefault("database.conn_max_lifetime", 30*time.Minute)
 
 	// Redis defaults
+	v.SetDefault("redis.url", "")
 	v.SetDefault("redis.host", "localhost")
 	v.SetDefault("redis.port", 6379)
 	v.SetDefault("redis.password", "")
@@ -76,36 +98,92 @@ func setDefaults(v *viper.Viper) {
 	v.SetDefault("redis.dial_timeout", 5*time.Second)
 	v.SetDefault("redis.read_timeout", 3*time.Second)
 	v.SetDefault("redis.write_timeout", 3*time.Second)
+	v.SetDefault("redis.tls_reject_unauthorized", true) // 与 Node.js 版本一致
+	v.SetDefault("redis.max_retries", 3)
+	v.SetDefault("redis.min_retry_backoff", 200*time.Millisecond)
+	v.SetDefault("redis.max_retry_backoff", 2*time.Second)
+	v.SetDefault("redis.enabled", true)
 
 	// Log defaults
-	v.SetDefault("log.level", "info")
+	v.SetDefault("log.level", "info") // 与 Node.js 版本一致
 	v.SetDefault("log.format", "json")
 
 	// Auth defaults
-	v.SetDefault("auth.admin_api_key", "")
+	v.SetDefault("auth.admin_token", "")
+
+	// MessageRequest defaults - 与 Node.js 版本一致
+	v.SetDefault("message_request.write_mode", "async")           // 默认异步写入
+	v.SetDefault("message_request.async_flush_interval_ms", 1000) // 1秒刷新间隔
+	v.SetDefault("message_request.async_batch_size", 100)         // 批量大小
+	v.SetDefault("message_request.async_max_pending", 10000)      // 最大待处理数
+
+	// Features defaults - 与 Node.js 版本一致
+	v.SetDefault("features.enable_rate_limit", true)
+	v.SetDefault("features.enable_secure_cookies", true)
+	v.SetDefault("features.enable_multi_provider_types", false)
+	v.SetDefault("features.enable_circuit_breaker_on_network_errors", false)
+	v.SetDefault("features.enable_provider_cache", true)
+	v.SetDefault("features.enable_smart_probing", false)
+
+	// Proxy defaults - 与 Node.js 版本一致
+	v.SetDefault("proxy.max_retry_attempts_default", 2)          // 范围 1-10
+	v.SetDefault("proxy.fetch_body_timeout", 600*time.Second)    // 600秒
+	v.SetDefault("proxy.fetch_headers_timeout", 600*time.Second) // 600秒
+	v.SetDefault("proxy.fetch_connect_timeout", 30*time.Second)  // 30秒
+
+	// Session defaults - 与 Node.js 版本一致
+	v.SetDefault("session.ttl", 300) // 300秒
+	v.SetDefault("session.store_session_messages", false)
+
+	// SmartProbing defaults - 与 Node.js 版本一致
+	v.SetDefault("smart_probing.interval_ms", 30000) // 30秒
+	v.SetDefault("smart_probing.timeout_ms", 5000)   // 5秒
+
+	// APITest defaults - 与 Node.js 版本一致
+	v.SetDefault("api_test.timeout_ms", 15000) // 15秒
+
+	// App defaults
+	v.SetDefault("app.url", "")
 }
 
 // bindEnvVariables 绑定环境变量
+// 环境变量名称与 Node.js 版本保持一致
 func bindEnvVariables(v *viper.Viper) {
-	// Server
-	_ = v.BindEnv("server.port", "SERVER_PORT")
+	// 环境模式
+	_ = v.BindEnv("env", "NODE_ENV")
+
+	// 时区
+	_ = v.BindEnv("timezone", "TZ")
+
+	// 调试模式
+	_ = v.BindEnv("debug_mode", "DEBUG_MODE")
+
+	// 自动迁移
+	_ = v.BindEnv("auto_migrate", "AUTO_MIGRATE")
+
+	// Server - 与 Node.js 版本保持一致
+	_ = v.BindEnv("server.port", "PORT")
 	_ = v.BindEnv("server.host", "SERVER_HOST")
 	_ = v.BindEnv("server.read_timeout", "SERVER_READ_TIMEOUT")
 	_ = v.BindEnv("server.write_timeout", "SERVER_WRITE_TIMEOUT")
 	_ = v.BindEnv("server.shutdown_timeout", "SERVER_SHUTDOWN_TIMEOUT")
 
-	// Database
+	// Database - 与 Node.js 版本保持一致
+	_ = v.BindEnv("database.dsn", "DSN")
 	_ = v.BindEnv("database.host", "DATABASE_HOST")
 	_ = v.BindEnv("database.port", "DATABASE_PORT")
 	_ = v.BindEnv("database.user", "DATABASE_USER")
 	_ = v.BindEnv("database.password", "DATABASE_PASSWORD")
 	_ = v.BindEnv("database.dbname", "DATABASE_NAME")
 	_ = v.BindEnv("database.sslmode", "DATABASE_SSLMODE")
-	_ = v.BindEnv("database.max_open_conns", "DATABASE_MAX_OPEN_CONNS")
+	_ = v.BindEnv("database.pool_max", "DB_POOL_MAX")
 	_ = v.BindEnv("database.max_idle_conns", "DATABASE_MAX_IDLE_CONNS")
-	_ = v.BindEnv("database.conn_lifetime", "DATABASE_CONN_LIFETIME")
+	_ = v.BindEnv("database.idle_timeout", "DB_POOL_IDLE_TIMEOUT")
+	_ = v.BindEnv("database.connect_timeout", "DB_POOL_CONNECT_TIMEOUT")
+	_ = v.BindEnv("database.conn_max_lifetime", "DATABASE_CONN_MAX_LIFETIME")
 
-	// Redis
+	// Redis - 与 Node.js 版本保持一致
+	_ = v.BindEnv("redis.url", "REDIS_URL")
 	_ = v.BindEnv("redis.host", "REDIS_HOST")
 	_ = v.BindEnv("redis.port", "REDIS_PORT")
 	_ = v.BindEnv("redis.password", "REDIS_PASSWORD")
@@ -115,11 +193,114 @@ func bindEnvVariables(v *viper.Viper) {
 	_ = v.BindEnv("redis.dial_timeout", "REDIS_DIAL_TIMEOUT")
 	_ = v.BindEnv("redis.read_timeout", "REDIS_READ_TIMEOUT")
 	_ = v.BindEnv("redis.write_timeout", "REDIS_WRITE_TIMEOUT")
+	_ = v.BindEnv("redis.tls_reject_unauthorized", "REDIS_TLS_REJECT_UNAUTHORIZED")
+	_ = v.BindEnv("redis.max_retries", "REDIS_MAX_RETRIES")
+	_ = v.BindEnv("redis.min_retry_backoff", "REDIS_MIN_RETRY_BACKOFF")
+	_ = v.BindEnv("redis.max_retry_backoff", "REDIS_MAX_RETRY_BACKOFF")
+	_ = v.BindEnv("redis.enabled", "REDIS_ENABLED")
 
-	// Log
+	// Log - 与 Node.js 版本保持一致
 	_ = v.BindEnv("log.level", "LOG_LEVEL")
 	_ = v.BindEnv("log.format", "LOG_FORMAT")
 
-	// Auth
-	_ = v.BindEnv("auth.admin_api_key", "ADMIN_API_KEY")
+	// Auth - 与 Node.js 版本保持一致
+	_ = v.BindEnv("auth.admin_token", "ADMIN_TOKEN")
+
+	// MessageRequest - 与 Node.js 版本保持一致
+	_ = v.BindEnv("message_request.write_mode", "MESSAGE_REQUEST_WRITE_MODE")
+	_ = v.BindEnv("message_request.async_flush_interval_ms", "MESSAGE_REQUEST_ASYNC_FLUSH_INTERVAL_MS")
+	_ = v.BindEnv("message_request.async_batch_size", "MESSAGE_REQUEST_ASYNC_BATCH_SIZE")
+	_ = v.BindEnv("message_request.async_max_pending", "MESSAGE_REQUEST_ASYNC_MAX_PENDING")
+
+	// Features - 与 Node.js 版本保持一致
+	_ = v.BindEnv("features.enable_rate_limit", "ENABLE_RATE_LIMIT")
+	_ = v.BindEnv("features.enable_secure_cookies", "ENABLE_SECURE_COOKIES")
+	_ = v.BindEnv("features.enable_multi_provider_types", "ENABLE_MULTI_PROVIDER_TYPES")
+	_ = v.BindEnv("features.enable_circuit_breaker_on_network_errors", "ENABLE_CIRCUIT_BREAKER_ON_NETWORK_ERRORS")
+	_ = v.BindEnv("features.enable_provider_cache", "ENABLE_PROVIDER_CACHE")
+
+	// Proxy - 与 Node.js 版本保持一致
+	_ = v.BindEnv("proxy.max_retry_attempts_default", "MAX_RETRY_ATTEMPTS_DEFAULT")
+	_ = v.BindEnv("proxy.fetch_body_timeout", "FETCH_BODY_TIMEOUT")
+	_ = v.BindEnv("proxy.fetch_headers_timeout", "FETCH_HEADERS_TIMEOUT")
+	_ = v.BindEnv("proxy.fetch_connect_timeout", "FETCH_CONNECT_TIMEOUT")
+
+	// Session - 与 Node.js 版本保持一致
+	_ = v.BindEnv("session.ttl", "SESSION_TTL")
+	_ = v.BindEnv("session.store_session_messages", "STORE_SESSION_MESSAGES")
+
+	// SmartProbing - 与 Node.js 版本保持一致
+	_ = v.BindEnv("features.enable_smart_probing", "ENABLE_SMART_PROBING")
+	_ = v.BindEnv("smart_probing.interval_ms", "PROBE_INTERVAL_MS")
+	_ = v.BindEnv("smart_probing.timeout_ms", "PROBE_TIMEOUT_MS")
+
+	// APITest - 与 Node.js 版本保持一致
+	_ = v.BindEnv("api_test.timeout_ms", "API_TEST_TIMEOUT_MS")
+
+	// App - 与 Node.js 版本保持一致
+	_ = v.BindEnv("app.url", "APP_URL")
+}
+
+// validate 验证配置
+func validate(cfg *Config) error {
+	// 验证环境模式
+	if cfg.Env != "development" && cfg.Env != "production" && cfg.Env != "test" {
+		return fmt.Errorf("invalid env: %s, must be one of: development, production, test", cfg.Env)
+	}
+
+	// 验证日志级别
+	validLogLevels := map[string]bool{
+		"fatal": true, "error": true, "warn": true,
+		"info": true, "debug": true, "trace": true,
+	}
+	if !validLogLevels[cfg.Log.Level] {
+		return fmt.Errorf("invalid log level: %s, must be one of: fatal, error, warn, info, debug, trace", cfg.Log.Level)
+	}
+
+	// 验证数据库连接池配置
+	if cfg.Database.PoolMax < 1 || cfg.Database.PoolMax > 200 {
+		return fmt.Errorf("invalid database.pool_max: %d, must be between 1 and 200", cfg.Database.PoolMax)
+	}
+
+	// 验证数据库空闲超时(秒)- 与 Node.js 版本一致
+	idleTimeoutSeconds := int(cfg.Database.IdleTimeout.Seconds())
+	if idleTimeoutSeconds < 0 || idleTimeoutSeconds > 3600 {
+		return fmt.Errorf("invalid database.idle_timeout: %d seconds, must be between 0 and 3600", idleTimeoutSeconds)
+	}
+
+	// 验证数据库连接超时(秒)- 与 Node.js 版本一致
+	connectTimeoutSeconds := int(cfg.Database.ConnectTimeout.Seconds())
+	if connectTimeoutSeconds < 1 || connectTimeoutSeconds > 120 {
+		return fmt.Errorf("invalid database.connect_timeout: %d seconds, must be between 1 and 120", connectTimeoutSeconds)
+	}
+
+	// 验证 API 测试超时(毫秒)- 与 Node.js 版本一致
+	if cfg.APITest.TimeoutMs < 5000 || cfg.APITest.TimeoutMs > 120000 {
+		return fmt.Errorf("invalid api_test.timeout_ms: %d, must be between 5000 and 120000", cfg.APITest.TimeoutMs)
+	}
+
+	// 验证消息请求写入模式
+	if cfg.MessageRequest.WriteMode != "sync" && cfg.MessageRequest.WriteMode != "async" {
+		return fmt.Errorf("invalid message_request.write_mode: %s, must be one of: sync, async", cfg.MessageRequest.WriteMode)
+	}
+
+	// 验证异步写入配置
+	if cfg.MessageRequest.WriteMode == "async" {
+		if cfg.MessageRequest.AsyncFlushIntervalMs < 10 || cfg.MessageRequest.AsyncFlushIntervalMs > 60000 {
+			return fmt.Errorf("invalid message_request.async_flush_interval_ms: %d, must be between 10 and 60000", cfg.MessageRequest.AsyncFlushIntervalMs)
+		}
+		if cfg.MessageRequest.AsyncBatchSize < 1 || cfg.MessageRequest.AsyncBatchSize > 2000 {
+			return fmt.Errorf("invalid message_request.async_batch_size: %d, must be between 1 and 2000", cfg.MessageRequest.AsyncBatchSize)
+		}
+		if cfg.MessageRequest.AsyncMaxPending < 100 || cfg.MessageRequest.AsyncMaxPending > 200000 {
+			return fmt.Errorf("invalid message_request.async_max_pending: %d, must be between 100 and 200000", cfg.MessageRequest.AsyncMaxPending)
+		}
+	}
+
+	// 验证代理配置
+	if cfg.Proxy.MaxRetryAttemptsDefault < 1 || cfg.Proxy.MaxRetryAttemptsDefault > 10 {
+		return fmt.Errorf("invalid proxy.max_retry_attempts_default: %d, must be between 1 and 10", cfg.Proxy.MaxRetryAttemptsDefault)
+	}
+
+	return nil
 }

+ 185 - 17
internal/database/postgres.go

@@ -3,7 +3,11 @@ package database
 import (
 	"context"
 	"database/sql"
+	"errors"
 	"fmt"
+	"strings"
+	"sync"
+	"time"
 
 	"github.com/ding113/claude-code-hub/internal/config"
 	"github.com/ding113/claude-code-hub/internal/pkg/logger"
@@ -12,50 +16,100 @@ import (
 	"github.com/uptrace/bun/driver/pgdriver"
 )
 
+var (
+	// 单例模式,与 Node.js 版本保持一致
+	dbInstance *bun.DB
+	dbOnce     sync.Once
+	dbErr      error
+)
+
+// PostgresDB 封装 PostgreSQL 数据库连接
+type PostgresDB struct {
+	DB  *bun.DB
+	cfg config.DatabaseConfig
+}
+
 // NewPostgres 创建 PostgreSQL 数据库连接
+// 支持两种配置方式:
+// 1. DSN 连接字符串(优先)
+// 2. 分离的配置字段
 func NewPostgres(cfg config.DatabaseConfig) (*bun.DB, error) {
-	// 构建 DSN
-	dsn := fmt.Sprintf(
-		"postgres://%s:%s@%s:%d/%s?sslmode=%s",
-		cfg.User,
-		cfg.Password,
-		cfg.Host,
-		cfg.Port,
-		cfg.DBName,
-		cfg.SSLMode,
-	)
+	// 获取 DSN
+	dsn := cfg.DSN
+	if dsn == "" {
+		// 如果没有 DSN,则从分离的配置字段构建
+		dsn = buildDSN(cfg)
+	}
+
+	// 验证 DSN 不为空
+	if dsn == "" {
+		return nil, errors.New("DSN environment variable is not set")
+	}
+
+	// 检查是否为占位符模板(与 Node.js 版本保持一致)
+	if strings.Contains(dsn, "user:password@host:port") {
+		return nil, errors.New("DSN contains placeholder template, please set a valid DSN")
+	}
 
 	// 创建连接器
 	connector := pgdriver.NewConnector(
 		pgdriver.WithDSN(dsn),
-		pgdriver.WithTimeout(cfg.ConnLifetime),
+		pgdriver.WithDialTimeout(cfg.ConnectTimeout),
+		pgdriver.WithReadTimeout(cfg.IdleTimeout), // 读取超时使用空闲超时
 	)
 
 	// 创建 sql.DB
 	sqlDB := sql.OpenDB(connector)
 
 	// 设置连接池参数
-	sqlDB.SetMaxOpenConns(cfg.MaxOpenConns)
+	// MaxOpenConns: 最大打开连接数
+	// - 与 Node.js 版本的 max 参数对应
+	sqlDB.SetMaxOpenConns(cfg.PoolMax)
+
+	// MaxIdleConns: 最大空闲连接数
 	sqlDB.SetMaxIdleConns(cfg.MaxIdleConns)
-	sqlDB.SetConnMaxLifetime(cfg.ConnLifetime)
+
+	// ConnMaxLifetime: 连接最大生命周期
+	sqlDB.SetConnMaxLifetime(cfg.ConnMaxLifetime)
+
+	// ConnMaxIdleTime: 空闲连接最大存活时间
+	// - 与 Node.js 版本的 idle_timeout 参数对应
+	sqlDB.SetConnMaxIdleTime(cfg.IdleTimeout)
 
 	// 创建 Bun DB
 	db := bun.NewDB(sqlDB, pgdialect.New())
 
 	// 测试连接
-	if err := db.PingContext(context.Background()); err != nil {
+	ctx, cancel := context.WithTimeout(context.Background(), cfg.ConnectTimeout)
+	defer cancel()
+
+	if err := db.PingContext(ctx); err != nil {
 		return nil, fmt.Errorf("failed to ping database: %w", err)
 	}
 
+	// 记录连接信息(隐藏敏感信息)
+	logDSN := sanitizeDSN(dsn)
 	logger.Info().
-		Str("host", cfg.Host).
-		Int("port", cfg.Port).
-		Str("database", cfg.DBName).
+		Str("dsn", logDSN).
+		Int("pool_max", cfg.PoolMax).
+		Int("max_idle_conns", cfg.MaxIdleConns).
+		Dur("idle_timeout", cfg.IdleTimeout).
+		Dur("connect_timeout", cfg.ConnectTimeout).
+		Dur("conn_max_lifetime", cfg.ConnMaxLifetime).
 		Msg("PostgreSQL connected")
 
 	return db, nil
 }
 
+// GetDB 获取数据库单例(懒加载)
+// 与 Node.js 版本的 getDb() 函数对应
+func GetDB(cfg config.DatabaseConfig) (*bun.DB, error) {
+	dbOnce.Do(func() {
+		dbInstance, dbErr = NewPostgres(cfg)
+	})
+	return dbInstance, dbErr
+}
+
 // ClosePostgres 关闭数据库连接
 func ClosePostgres(db *bun.DB) error {
 	if db != nil {
@@ -64,3 +118,117 @@ func ClosePostgres(db *bun.DB) error {
 	}
 	return nil
 }
+
+// HealthCheck 健康检查
+// 返回数据库连接状态和统计信息
+func HealthCheck(ctx context.Context, db *bun.DB) (*HealthStatus, error) {
+	if db == nil {
+		return nil, errors.New("database connection is nil")
+	}
+
+	status := &HealthStatus{
+		Healthy:   false,
+		Timestamp: time.Now(),
+	}
+
+	// 执行 ping 检查
+	start := time.Now()
+	err := db.PingContext(ctx)
+	status.Latency = time.Since(start)
+
+	if err != nil {
+		status.Error = err.Error()
+		return status, err
+	}
+
+	status.Healthy = true
+
+	// 获取连接池统计信息
+	sqlDB := db.DB
+	stats := sqlDB.Stats()
+	status.Stats = &PoolStats{
+		MaxOpenConnections: stats.MaxOpenConnections,
+		OpenConnections:    stats.OpenConnections,
+		InUse:              stats.InUse,
+		Idle:               stats.Idle,
+		WaitCount:          stats.WaitCount,
+		WaitDuration:       stats.WaitDuration,
+		MaxIdleClosed:      stats.MaxIdleClosed,
+		MaxLifetimeClosed:  stats.MaxLifetimeClosed,
+	}
+
+	return status, nil
+}
+
+// HealthStatus 健康检查状态
+type HealthStatus struct {
+	Healthy   bool          `json:"healthy"`
+	Latency   time.Duration `json:"latency"`
+	Error     string        `json:"error,omitempty"`
+	Timestamp time.Time     `json:"timestamp"`
+	Stats     *PoolStats    `json:"stats,omitempty"`
+}
+
+// PoolStats 连接池统计信息
+type PoolStats struct {
+	MaxOpenConnections int           `json:"max_open_connections"`
+	OpenConnections    int           `json:"open_connections"`
+	InUse              int           `json:"in_use"`
+	Idle               int           `json:"idle"`
+	WaitCount          int64         `json:"wait_count"`
+	WaitDuration       time.Duration `json:"wait_duration"`
+	MaxIdleClosed      int64         `json:"max_idle_closed"`
+	MaxLifetimeClosed  int64         `json:"max_lifetime_closed"`
+}
+
+// buildDSN 从分离的配置字段构建 DSN
+func buildDSN(cfg config.DatabaseConfig) string {
+	if cfg.Host == "" {
+		return ""
+	}
+
+	return fmt.Sprintf(
+		"postgres://%s:%s@%s:%d/%s?sslmode=%s",
+		cfg.User,
+		cfg.Password,
+		cfg.Host,
+		cfg.Port,
+		cfg.DBName,
+		cfg.SSLMode,
+	)
+}
+
+// sanitizeDSN 清理 DSN 中的敏感信息(用于日志)
+func sanitizeDSN(dsn string) string {
+	// 简单处理:隐藏密码部分
+	// postgres://user:password@host:port/dbname -> postgres://user:***@host:port/dbname
+	if !strings.Contains(dsn, "://") {
+		return dsn
+	}
+
+	parts := strings.SplitN(dsn, "://", 2)
+	if len(parts) != 2 {
+		return dsn
+	}
+
+	protocol := parts[0]
+	rest := parts[1]
+
+	// 查找 @ 符号
+	atIndex := strings.Index(rest, "@")
+	if atIndex == -1 {
+		return dsn
+	}
+
+	userPass := rest[:atIndex]
+	hostAndRest := rest[atIndex:]
+
+	// 查找密码部分
+	colonIndex := strings.Index(userPass, ":")
+	if colonIndex == -1 {
+		return dsn
+	}
+
+	user := userPass[:colonIndex]
+	return fmt.Sprintf("%s://%s:***%s", protocol, user, hostAndRest)
+}

+ 378 - 21
internal/database/redis.go

@@ -2,46 +2,403 @@ package database
 
 import (
 	"context"
+	"crypto/tls"
 	"fmt"
+	"net/url"
+	"strconv"
+	"strings"
+	"time"
 
 	"github.com/ding113/claude-code-hub/internal/config"
 	"github.com/ding113/claude-code-hub/internal/pkg/logger"
 	"github.com/redis/go-redis/v9"
 )
 
+// 默认配置常量(与 Node.js 版本对齐)
+const (
+	defaultMaxRetries      = 3
+	defaultMinRetryBackoff = 200 * time.Millisecond
+	defaultMaxRetryBackoff = 2 * time.Second
+	defaultPoolSize        = 10
+	defaultMinIdleConns    = 2
+	defaultDialTimeout     = 5 * time.Second
+	defaultReadTimeout     = 3 * time.Second
+	defaultWriteTimeout    = 3 * time.Second
+)
+
+// maskRedisURL 对 Redis URL 中的密码进行脱敏处理
+// 与 Node.js 版本的 maskRedisUrl 函数功能对齐
+func maskRedisURL(redisURL string) string {
+	if redisURL == "" {
+		return ""
+	}
+
+	parsed, err := url.Parse(redisURL)
+	if err != nil {
+		// 解析失败时使用正则替换
+		// 匹配 :password@ 格式
+		if idx := strings.Index(redisURL, "@"); idx != -1 {
+			colonIdx := strings.LastIndex(redisURL[:idx], ":")
+			if colonIdx != -1 {
+				return redisURL[:colonIdx+1] + "****" + redisURL[idx:]
+			}
+		}
+		return redisURL
+	}
+
+	if _, hasPassword := parsed.User.Password(); hasPassword {
+		parsed.User = url.UserPassword(parsed.User.Username(), "****")
+	}
+	return parsed.String()
+}
+
+// isTLSEnabled 检测 URL 是否使用 TLS(rediss:// 协议)
+func isTLSEnabled(redisURL string) bool {
+	if redisURL == "" {
+		return false
+	}
+
+	parsed, err := url.Parse(redisURL)
+	if err != nil {
+		// 解析失败时使用字符串前缀检测
+		return strings.HasPrefix(redisURL, "rediss://")
+	}
+	return parsed.Scheme == "rediss"
+}
+
+// parseRedisURL 解析 Redis URL 并返回连接选项
+// 支持 redis:// 和 rediss:// 协议
+func parseRedisURL(redisURL string) (*redis.Options, error) {
+	// go-redis 内置支持 URL 解析
+	opts, err := redis.ParseURL(redisURL)
+	if err != nil {
+		return nil, fmt.Errorf("failed to parse redis URL: %w", err)
+	}
+	return opts, nil
+}
+
+// buildTLSConfig 构建 TLS 配置
+// 与 Node.js 版本的 buildTlsConfig 函数功能对齐
+// 支持 SNI (Server Name Indication) 和跳过证书验证
+func buildTLSConfig(redisURL string, rejectUnauthorized bool) *tls.Config {
+	tlsConfig := &tls.Config{
+		InsecureSkipVerify: !rejectUnauthorized,
+	}
+
+	// 从 URL 中提取 hostname 用于 SNI
+	if redisURL != "" {
+		parsed, err := url.Parse(redisURL)
+		if err == nil && parsed.Hostname() != "" {
+			tlsConfig.ServerName = parsed.Hostname()
+		}
+	}
+
+	return tlsConfig
+}
+
+// buildRedisOptions 根据配置构建 Redis 连接选项
+// 与 Node.js 版本的 buildRedisOptionsForUrl 函数功能对齐
+func buildRedisOptions(cfg config.RedisConfig) (*redis.Options, bool, error) {
+	var opts *redis.Options
+	var useTLS bool
+
+	// 优先使用 URL 方式
+	if cfg.URL != "" {
+		var err error
+		opts, err = parseRedisURL(cfg.URL)
+		if err != nil {
+			return nil, false, err
+		}
+		useTLS = isTLSEnabled(cfg.URL)
+
+		// 如果使用 TLS,配置 TLS 选项
+		if useTLS {
+			opts.TLSConfig = buildTLSConfig(cfg.URL, cfg.TLSRejectUnauthorized)
+		}
+	} else {
+		// 使用 Host:Port 方式
+		port := cfg.Port
+		if port == 0 {
+			port = 6379
+		}
+
+		opts = &redis.Options{
+			Addr:     fmt.Sprintf("%s:%d", cfg.Host, port),
+			Password: cfg.Password,
+			DB:       cfg.DB,
+		}
+	}
+
+	// 应用连接池配置
+	if cfg.PoolSize > 0 {
+		opts.PoolSize = cfg.PoolSize
+	} else if opts.PoolSize == 0 {
+		opts.PoolSize = defaultPoolSize
+	}
+
+	if cfg.MinIdleConns > 0 {
+		opts.MinIdleConns = cfg.MinIdleConns
+	} else if opts.MinIdleConns == 0 {
+		opts.MinIdleConns = defaultMinIdleConns
+	}
+
+	// 应用超时配置
+	if cfg.DialTimeout > 0 {
+		opts.DialTimeout = cfg.DialTimeout
+	} else if opts.DialTimeout == 0 {
+		opts.DialTimeout = defaultDialTimeout
+	}
+
+	if cfg.ReadTimeout > 0 {
+		opts.ReadTimeout = cfg.ReadTimeout
+	} else if opts.ReadTimeout == 0 {
+		opts.ReadTimeout = defaultReadTimeout
+	}
+
+	if cfg.WriteTimeout > 0 {
+		opts.WriteTimeout = cfg.WriteTimeout
+	} else if opts.WriteTimeout == 0 {
+		opts.WriteTimeout = defaultWriteTimeout
+	}
+
+	// 应用重试配置(与 Node.js 版本对齐)
+	if cfg.MaxRetries > 0 {
+		opts.MaxRetries = cfg.MaxRetries
+	} else {
+		opts.MaxRetries = defaultMaxRetries
+	}
+
+	if cfg.MinRetryBackoff > 0 {
+		opts.MinRetryBackoff = cfg.MinRetryBackoff
+	} else {
+		opts.MinRetryBackoff = defaultMinRetryBackoff
+	}
+
+	if cfg.MaxRetryBackoff > 0 {
+		opts.MaxRetryBackoff = cfg.MaxRetryBackoff
+	} else {
+		opts.MaxRetryBackoff = defaultMaxRetryBackoff
+	}
+
+	// 禁用离线队列,实现快速失败(与 Node.js 版本的 enableOfflineQueue: false 对齐)
+	// go-redis 默认不使用离线队列,但我们显式设置 PoolTimeout 来实现快速失败
+	opts.PoolTimeout = opts.ReadTimeout
+
+	return opts, useTLS, nil
+}
+
+// RedisClient 封装 Redis 客户端,提供额外的功能
+type RedisClient struct {
+	*redis.Client
+	useTLS  bool
+	safeURL string // 脱敏后的 URL
+}
+
 // NewRedis 创建 Redis 客户端
-func NewRedis(cfg config.RedisConfig) (*redis.Client, error) {
-	client := redis.NewClient(&redis.Options{
-		Addr:         fmt.Sprintf("%s:%d", cfg.Host, cfg.Port),
-		Password:     cfg.Password,
-		DB:           cfg.DB,
-		PoolSize:     cfg.PoolSize,
-		MinIdleConns: cfg.MinIdleConns,
-		DialTimeout:  cfg.DialTimeout,
-		ReadTimeout:  cfg.ReadTimeout,
-		WriteTimeout: cfg.WriteTimeout,
-	})
+// 与 Node.js 版本的 getRedisClient 函数功能对齐
+// 支持 URL 和 Host:Port 两种配置方式,自动检测 TLS
+func NewRedis(cfg config.RedisConfig) (*RedisClient, error) {
+	// 检查是否启用 Redis
+	if !cfg.Enabled {
+		logger.Warn().Msg("[Redis] Redis disabled (enabled=false)")
+		return nil, nil
+	}
+
+	// 检查配置是否有效
+	if cfg.URL == "" && cfg.Host == "" {
+		logger.Warn().Msg("[Redis] Redis URL or Host not configured")
+		return nil, nil
+	}
+
+	// 获取脱敏后的 URL 用于日志
+	safeURL := ""
+	if cfg.URL != "" {
+		safeURL = maskRedisURL(cfg.URL)
+	} else {
+		safeURL = fmt.Sprintf("%s:%d", cfg.Host, cfg.Port)
+	}
+
+	// 构建连接选项
+	opts, useTLS, err := buildRedisOptions(cfg)
+	if err != nil {
+		logger.Error().
+			Err(err).
+			Str("redisUrl", safeURL).
+			Msg("[Redis] Failed to build options")
+		return nil, err
+	}
+
+	// 记录 TLS 配置信息
+	if useTLS {
+		logger.Info().
+			Str("redisUrl", safeURL).
+			Bool("rejectUnauthorized", cfg.TLSRejectUnauthorized).
+			Msg("[Redis] Using TLS connection (rediss://)")
+	}
+
+	// 创建客户端
+	client := redis.NewClient(opts)
 
 	// 测试连接
-	ctx := context.Background()
+	ctx, cancel := context.WithTimeout(context.Background(), opts.DialTimeout)
+	defer cancel()
+
 	if err := client.Ping(ctx).Err(); err != nil {
+		logger.Error().
+			Err(err).
+			Str("protocol", protocolName(useTLS)).
+			Bool("tlsEnabled", useTLS).
+			Str("redisUrl", safeURL).
+			Msg("[Redis] Connection error")
 		return nil, fmt.Errorf("failed to ping redis: %w", err)
 	}
 
+	// 连接成功日志(与 Node.js 版本对齐)
 	logger.Info().
-		Str("host", cfg.Host).
-		Int("port", cfg.Port).
-		Int("db", cfg.DB).
-		Msg("Redis connected")
+		Str("protocol", protocolName(useTLS)).
+		Bool("tlsEnabled", useTLS).
+		Str("redisUrl", safeURL).
+		Int("poolSize", opts.PoolSize).
+		Int("minIdleConns", opts.MinIdleConns).
+		Int("maxRetries", opts.MaxRetries).
+		Msg("[Redis] Connected successfully")
+
+	return &RedisClient{
+		Client:  client,
+		useTLS:  useTLS,
+		safeURL: safeURL,
+	}, nil
+}
+
+// protocolName 返回协议名称
+func protocolName(useTLS bool) string {
+	if useTLS {
+		return "rediss"
+	}
+	return "redis"
+}
 
-	return client, nil
+// HealthCheck 执行健康检查
+func (c *RedisClient) HealthCheck(ctx context.Context) error {
+	if c == nil || c.Client == nil {
+		return fmt.Errorf("redis client is nil")
+	}
+
+	start := time.Now()
+	err := c.Client.Ping(ctx).Err()
+	latency := time.Since(start)
+
+	if err != nil {
+		logger.Error().
+			Err(err).
+			Str("redisUrl", c.safeURL).
+			Dur("latency", latency).
+			Msg("[Redis] Health check failed")
+		return err
+	}
+
+	logger.Debug().
+		Str("redisUrl", c.safeURL).
+		Dur("latency", latency).
+		Msg("[Redis] Health check passed")
+
+	return nil
+}
+
+// GetPoolStats 获取连接池统计信息
+func (c *RedisClient) GetPoolStats() *redis.PoolStats {
+	if c == nil || c.Client == nil {
+		return nil
+	}
+	return c.Client.PoolStats()
 }
 
 // CloseRedis 关闭 Redis 连接
-func CloseRedis(client *redis.Client) error {
-	if client != nil {
-		logger.Info().Msg("Closing Redis connection")
-		return client.Close()
+// 与 Node.js 版本的 closeRedis 函数功能对齐
+func CloseRedis(client *RedisClient) error {
+	if client == nil || client.Client == nil {
+		return nil
+	}
+
+	logger.Info().
+		Str("redisUrl", client.safeURL).
+		Msg("[Redis] Closing connection")
+
+	if err := client.Client.Close(); err != nil {
+		logger.Error().
+			Err(err).
+			Str("redisUrl", client.safeURL).
+			Msg("[Redis] Failed to close connection")
+		return err
 	}
+
+	logger.Info().
+		Str("redisUrl", client.safeURL).
+		Msg("[Redis] Connection closed")
+
 	return nil
 }
+
+// GetRedisClient 获取 Redis 客户端(兼容旧接口)
+// 返回底层的 *redis.Client
+func (c *RedisClient) GetRedisClient() *redis.Client {
+	if c == nil {
+		return nil
+	}
+	return c.Client
+}
+
+// ParseRedisInfo 解析 Redis INFO 命令的输出
+func ParseRedisInfo(info string) map[string]string {
+	result := make(map[string]string)
+	lines := strings.Split(info, "\n")
+	for _, line := range lines {
+		line = strings.TrimSpace(line)
+		if line == "" || strings.HasPrefix(line, "#") {
+			continue
+		}
+		parts := strings.SplitN(line, ":", 2)
+		if len(parts) == 2 {
+			result[parts[0]] = parts[1]
+		}
+	}
+	return result
+}
+
+// GetRedisVersion 获取 Redis 服务器版本
+func (c *RedisClient) GetRedisVersion(ctx context.Context) (string, error) {
+	if c == nil || c.Client == nil {
+		return "", fmt.Errorf("redis client is nil")
+	}
+
+	info, err := c.Client.Info(ctx, "server").Result()
+	if err != nil {
+		return "", err
+	}
+
+	parsed := ParseRedisInfo(info)
+	if version, ok := parsed["redis_version"]; ok {
+		return version, nil
+	}
+
+	return "", fmt.Errorf("redis_version not found in INFO output")
+}
+
+// GetMemoryUsage 获取 Redis 内存使用情况
+func (c *RedisClient) GetMemoryUsage(ctx context.Context) (int64, error) {
+	if c == nil || c.Client == nil {
+		return 0, fmt.Errorf("redis client is nil")
+	}
+
+	info, err := c.Client.Info(ctx, "memory").Result()
+	if err != nil {
+		return 0, err
+	}
+
+	parsed := ParseRedisInfo(info)
+	if usedMemory, ok := parsed["used_memory"]; ok {
+		return strconv.ParseInt(usedMemory, 10, 64)
+	}
+
+	return 0, fmt.Errorf("used_memory not found in INFO output")
+}

+ 122 - 0
internal/model/enums.go

@@ -0,0 +1,122 @@
+package model
+
+// DailyResetMode 日配额重置模式
+type DailyResetMode string
+
+const (
+	DailyResetModeFixed   DailyResetMode = "fixed"   // 固定时间重置
+	DailyResetModeRolling DailyResetMode = "rolling" // 滚动窗口(24小时)
+)
+
+// WebhookProviderType Webhook 提供商类型
+type WebhookProviderType string
+
+const (
+	WebhookProviderTypeWechat   WebhookProviderType = "wechat"
+	WebhookProviderTypeFeishu   WebhookProviderType = "feishu"
+	WebhookProviderTypeDingtalk WebhookProviderType = "dingtalk"
+	WebhookProviderTypeTelegram WebhookProviderType = "telegram"
+	WebhookProviderTypeCustom   WebhookProviderType = "custom"
+)
+
+// NotificationType 通知类型
+type NotificationType string
+
+const (
+	NotificationTypeCircuitBreaker   NotificationType = "circuit_breaker"
+	NotificationTypeDailyLeaderboard NotificationType = "daily_leaderboard"
+	NotificationTypeCostAlert        NotificationType = "cost_alert"
+)
+
+// ProviderType 供应商类型
+type ProviderType string
+
+const (
+	ProviderTypeClaude           ProviderType = "claude"
+	ProviderTypeClaudeAuth       ProviderType = "claude-auth"
+	ProviderTypeCodex            ProviderType = "codex"
+	ProviderTypeGeminiCli        ProviderType = "gemini-cli"
+	ProviderTypeGemini           ProviderType = "gemini"
+	ProviderTypeOpenAICompatible ProviderType = "openai-compatible"
+)
+
+// McpPassthroughType MCP 透传类型
+type McpPassthroughType string
+
+const (
+	McpPassthroughTypeNone    McpPassthroughType = "none"
+	McpPassthroughTypeMinimax McpPassthroughType = "minimax"
+	McpPassthroughTypeGlm     McpPassthroughType = "glm"
+	McpPassthroughTypeCustom  McpPassthroughType = "custom"
+)
+
+// ErrorRuleMatchType 错误规则匹配类型
+type ErrorRuleMatchType string
+
+const (
+	ErrorRuleMatchTypeRegex    ErrorRuleMatchType = "regex"
+	ErrorRuleMatchTypeContains ErrorRuleMatchType = "contains"
+	ErrorRuleMatchTypeExact    ErrorRuleMatchType = "exact"
+)
+
+// RequestFilterScope 请求过滤器作用域
+type RequestFilterScope string
+
+const (
+	RequestFilterScopeHeader RequestFilterScope = "header"
+	RequestFilterScopeBody   RequestFilterScope = "body"
+)
+
+// RequestFilterAction 请求过滤器动作
+type RequestFilterAction string
+
+const (
+	RequestFilterActionRemove      RequestFilterAction = "remove"
+	RequestFilterActionSet         RequestFilterAction = "set"
+	RequestFilterActionJsonPath    RequestFilterAction = "json_path"
+	RequestFilterActionTextReplace RequestFilterAction = "text_replace"
+)
+
+// RequestFilterBindingType 请求过滤器绑定类型
+type RequestFilterBindingType string
+
+const (
+	RequestFilterBindingTypeGlobal    RequestFilterBindingType = "global"
+	RequestFilterBindingTypeProviders RequestFilterBindingType = "providers"
+	RequestFilterBindingTypeGroups    RequestFilterBindingType = "groups"
+)
+
+// SensitiveWordMatchType 敏感词匹配类型
+type SensitiveWordMatchType string
+
+const (
+	SensitiveWordMatchTypeContains SensitiveWordMatchType = "contains"
+	SensitiveWordMatchTypeExact    SensitiveWordMatchType = "exact"
+	SensitiveWordMatchTypeRegex    SensitiveWordMatchType = "regex"
+)
+
+// Context1mPreference 1M Context Window 偏好
+type Context1mPreference string
+
+const (
+	Context1mPreferenceInherit     Context1mPreference = "inherit"
+	Context1mPreferenceForceEnable Context1mPreference = "force_enable"
+	Context1mPreferenceDisabled    Context1mPreference = "disabled"
+)
+
+// CodexInstructionsStrategy Codex instructions 策略
+type CodexInstructionsStrategy string
+
+const (
+	CodexInstructionsStrategyAuto          CodexInstructionsStrategy = "auto"
+	CodexInstructionsStrategyForceOfficial CodexInstructionsStrategy = "force_official"
+	CodexInstructionsStrategyKeepOriginal  CodexInstructionsStrategy = "keep_original"
+)
+
+// BillingModelSource 计费模型来源
+type BillingModelSource string
+
+const (
+	BillingModelSourceOriginal   BillingModelSource = "original"   // 重定向前
+	BillingModelSourceRedirected BillingModelSource = "redirected" // 重定向后
+)

+ 38 - 0
internal/model/error_rule.go

@@ -0,0 +1,38 @@
+package model
+
+import (
+	"time"
+
+	"github.com/uptrace/bun"
+)
+
+// ErrorRule 错误规则模型
+type ErrorRule struct {
+	bun.BaseModel `bun:"table:error_rules,alias:er"`
+
+	ID          int     `bun:"id,pk,autoincrement" json:"id"`
+	Pattern     string  `bun:"pattern,notnull" json:"pattern"`
+	MatchType   string  `bun:"match_type,notnull,default:'regex'" json:"matchType"` // regex, contains, exact
+	Category    string  `bun:"category,notnull" json:"category"`
+	Description *string `bun:"description" json:"description"`
+
+	// 覆写响应体(JSONB):匹配成功时用此响应替换原始错误响应
+	// 格式参考 Claude API: { type: "error", error: { type: "...", message: "..." }, request_id?: "..." }
+	// null = 不覆写,保留原始错误响应
+	OverrideResponse map[string]interface{} `bun:"override_response,type:jsonb" json:"overrideResponse"`
+
+	// 覆写状态码:null = 透传上游状态码
+	OverrideStatusCode *int `bun:"override_status_code" json:"overrideStatusCode"`
+
+	IsEnabled bool `bun:"is_enabled,notnull,default:true" json:"isEnabled"`
+	IsDefault bool `bun:"is_default,notnull,default:false" json:"isDefault"`
+	Priority  int  `bun:"priority,notnull,default:0" json:"priority"`
+
+	CreatedAt time.Time `bun:"created_at,notnull,default:current_timestamp" json:"createdAt"`
+	UpdatedAt time.Time `bun:"updated_at,notnull,default:current_timestamp" json:"updatedAt"`
+}
+
+// IsActive 检查错误规则是否处于活跃状态
+func (e *ErrorRule) IsActive() bool {
+	return e.IsEnabled
+}

+ 1 - 1
internal/model/key.go

@@ -39,7 +39,7 @@ type Key struct {
 	// 权限
 	CanLoginWebUi bool `bun:"can_login_web_ui,default:false" json:"canLoginWebUi"`
 
-	IsEnabled bool       `bun:"is_enabled,notnull,default:true" json:"isEnabled"`
+	IsEnabled bool       `bun:"is_enabled,default:true" json:"isEnabled"`
 	ExpiresAt *time.Time `bun:"expires_at" json:"expiresAt"`
 
 	CreatedAt time.Time  `bun:"created_at,notnull,default:current_timestamp" json:"createdAt"`

+ 56 - 0
internal/model/notification_settings.go

@@ -0,0 +1,56 @@
+package model
+
+import (
+	"time"
+
+	"github.com/quagmt/udecimal"
+	"github.com/uptrace/bun"
+)
+
+// NotificationSettings 通知设置模型
+type NotificationSettings struct {
+	bun.BaseModel `bun:"table:notification_settings,alias:ns"`
+
+	ID int `bun:"id,pk,autoincrement" json:"id"`
+
+	// 全局开关
+	Enabled bool `bun:"enabled,notnull,default:false" json:"enabled"`
+
+	// 兼容旧配置:默认使用 legacy 字段(单 URL / 自动识别),创建新目标后会切到新模式
+	UseLegacyMode bool `bun:"use_legacy_mode,notnull,default:false" json:"useLegacyMode"`
+
+	// 熔断器告警配置
+	CircuitBreakerEnabled bool    `bun:"circuit_breaker_enabled,notnull,default:false" json:"circuitBreakerEnabled"`
+	CircuitBreakerWebhook *string `bun:"circuit_breaker_webhook" json:"circuitBreakerWebhook"`
+
+	// 每日用户消费排行榜配置
+	DailyLeaderboardEnabled bool    `bun:"daily_leaderboard_enabled,notnull,default:false" json:"dailyLeaderboardEnabled"`
+	DailyLeaderboardWebhook *string `bun:"daily_leaderboard_webhook" json:"dailyLeaderboardWebhook"`
+	DailyLeaderboardTime    string  `bun:"daily_leaderboard_time,default:'09:00'" json:"dailyLeaderboardTime"` // HH:mm 格式
+	DailyLeaderboardTopN    *int    `bun:"daily_leaderboard_top_n,default:5" json:"dailyLeaderboardTopN"`      // 显示前 N 名
+
+	// 成本预警配置
+	CostAlertEnabled       bool             `bun:"cost_alert_enabled,notnull,default:false" json:"costAlertEnabled"`
+	CostAlertWebhook       *string          `bun:"cost_alert_webhook" json:"costAlertWebhook"`
+	CostAlertThreshold     udecimal.Decimal `bun:"cost_alert_threshold,type:numeric(5,2),default:0.80" json:"costAlertThreshold"` // 阈值 0-1 (80% = 0.80)
+	CostAlertCheckInterval *int             `bun:"cost_alert_check_interval,default:60" json:"costAlertCheckInterval"`            // 检查间隔(分钟)
+
+	CreatedAt time.Time `bun:"created_at,notnull,default:current_timestamp" json:"createdAt"`
+	UpdatedAt time.Time `bun:"updated_at,notnull,default:current_timestamp" json:"updatedAt"`
+}
+
+// GetDailyLeaderboardTopN 获取排行榜显示数量
+func (n *NotificationSettings) GetDailyLeaderboardTopN() int {
+	if n.DailyLeaderboardTopN == nil {
+		return 5
+	}
+	return *n.DailyLeaderboardTopN
+}
+
+// GetCostAlertCheckInterval 获取成本预警检查间隔(分钟)
+func (n *NotificationSettings) GetCostAlertCheckInterval() int {
+	if n.CostAlertCheckInterval == nil {
+		return 60
+	}
+	return *n.CostAlertCheckInterval
+}

+ 43 - 0
internal/model/notification_target_binding.go

@@ -0,0 +1,43 @@
+package model
+
+import (
+	"time"
+
+	"github.com/uptrace/bun"
+)
+
+// NotificationTargetBinding 通知目标绑定模型
+type NotificationTargetBinding struct {
+	bun.BaseModel `bun:"table:notification_target_bindings,alias:ntb"`
+
+	ID               int    `bun:"id,pk,autoincrement" json:"id"`
+	NotificationType string `bun:"notification_type,notnull" json:"notificationType"` // circuit_breaker, daily_leaderboard, cost_alert
+	TargetID         int    `bun:"target_id,notnull" json:"targetId"`
+
+	IsEnabled bool `bun:"is_enabled,notnull,default:true" json:"isEnabled"`
+
+	// 定时配置覆盖(可选,仅用于定时类通知)
+	ScheduleCron     *string `bun:"schedule_cron" json:"scheduleCron"`
+	ScheduleTimezone string  `bun:"schedule_timezone,default:'Asia/Shanghai'" json:"scheduleTimezone"`
+
+	// 模板覆盖(可选,主要用于 custom webhook)
+	TemplateOverride map[string]interface{} `bun:"template_override,type:jsonb" json:"templateOverride"`
+
+	CreatedAt time.Time `bun:"created_at,notnull,default:current_timestamp" json:"createdAt"`
+
+	// 关联
+	Target *WebhookTarget `bun:"rel:belongs-to,join:target_id=id" json:"target,omitempty"`
+}
+
+// IsActive 检查绑定是否处于活跃状态
+func (n *NotificationTargetBinding) IsActive() bool {
+	return n.IsEnabled
+}
+
+// GetScheduleTimezone 获取时区配置
+func (n *NotificationTargetBinding) GetScheduleTimezone() string {
+	if n.ScheduleTimezone == "" {
+		return "Asia/Shanghai"
+	}
+	return n.ScheduleTimezone
+}

+ 84 - 0
internal/model/request_filter.go

@@ -0,0 +1,84 @@
+package model
+
+import (
+	"time"
+
+	"github.com/uptrace/bun"
+)
+
+// RequestFilter 请求过滤器模型
+type RequestFilter struct {
+	bun.BaseModel `bun:"table:request_filters,alias:rf"`
+
+	ID          int     `bun:"id,pk,autoincrement" json:"id"`
+	Name        string  `bun:"name,notnull" json:"name"`
+	Description *string `bun:"description" json:"description"`
+
+	// 作用域:header 或 body
+	Scope string `bun:"scope,notnull" json:"scope"` // header, body
+
+	// 动作类型
+	Action string `bun:"action,notnull" json:"action"` // remove, set, json_path, text_replace
+
+	// 匹配类型(可选)
+	MatchType *string `bun:"match_type" json:"matchType"`
+
+	// 目标(要匹配/操作的字段或路径)
+	Target string `bun:"target,notnull" json:"target"`
+
+	// 替换值(JSONB)
+	Replacement interface{} `bun:"replacement,type:jsonb" json:"replacement"`
+
+	// 优先级
+	Priority int `bun:"priority,notnull,default:0" json:"priority"`
+
+	// 是否启用
+	IsEnabled bool `bun:"is_enabled,notnull,default:true" json:"isEnabled"`
+
+	// 绑定类型
+	BindingType string `bun:"binding_type,notnull,default:'global'" json:"bindingType"` // global, providers, groups
+
+	// 绑定的供应商 ID 列表
+	ProviderIds []int `bun:"provider_ids,type:jsonb" json:"providerIds"`
+
+	// 绑定的分组标签列表
+	GroupTags []string `bun:"group_tags,type:jsonb" json:"groupTags"`
+
+	CreatedAt time.Time `bun:"created_at,notnull,default:current_timestamp" json:"createdAt"`
+	UpdatedAt time.Time `bun:"updated_at,notnull,default:current_timestamp" json:"updatedAt"`
+}
+
+// IsActive 检查请求过滤器是否处于活跃状态
+func (r *RequestFilter) IsActive() bool {
+	return r.IsEnabled
+}
+
+// IsGlobal 检查是否为全局过滤器
+func (r *RequestFilter) IsGlobal() bool {
+	return r.BindingType == string(RequestFilterBindingTypeGlobal)
+}
+
+// AppliesToProvider 检查过滤器是否适用于指定供应商
+func (r *RequestFilter) AppliesToProvider(providerID int, groupTag *string) bool {
+	if r.IsGlobal() {
+		return true
+	}
+
+	if r.BindingType == string(RequestFilterBindingTypeProviders) {
+		for _, id := range r.ProviderIds {
+			if id == providerID {
+				return true
+			}
+		}
+	}
+
+	if r.BindingType == string(RequestFilterBindingTypeGroups) && groupTag != nil {
+		for _, tag := range r.GroupTags {
+			if tag == *groupTag {
+				return true
+			}
+		}
+	}
+
+	return false
+}

+ 26 - 0
internal/model/sensitive_word.go

@@ -0,0 +1,26 @@
+package model
+
+import (
+	"time"
+
+	"github.com/uptrace/bun"
+)
+
+// SensitiveWord 敏感词模型
+type SensitiveWord struct {
+	bun.BaseModel `bun:"table:sensitive_words,alias:sw"`
+
+	ID          int     `bun:"id,pk,autoincrement" json:"id"`
+	Word        string  `bun:"word,notnull" json:"word"`
+	MatchType   string  `bun:"match_type,notnull,default:'contains'" json:"matchType"` // contains, exact, regex
+	Description *string `bun:"description" json:"description"`
+	IsEnabled   bool    `bun:"is_enabled,notnull,default:true" json:"isEnabled"`
+
+	CreatedAt time.Time `bun:"created_at,notnull,default:current_timestamp" json:"createdAt"`
+	UpdatedAt time.Time `bun:"updated_at,notnull,default:current_timestamp" json:"updatedAt"`
+}
+
+// IsActive 检查敏感词是否处于活跃状态
+func (s *SensitiveWord) IsActive() bool {
+	return s.IsEnabled
+}

+ 62 - 0
internal/model/system_settings.go

@@ -0,0 +1,62 @@
+package model
+
+import (
+	"time"
+
+	"github.com/uptrace/bun"
+)
+
+// SystemSettings 系统设置模型
+type SystemSettings struct {
+	bun.BaseModel `bun:"table:system_settings,alias:ss"`
+
+	ID        int    `bun:"id,pk,autoincrement" json:"id"`
+	SiteTitle string `bun:"site_title,notnull,default:'Claude Code Hub'" json:"siteTitle"`
+
+	// 允许全局使用量查看
+	AllowGlobalUsageView bool `bun:"allow_global_usage_view,notnull,default:false" json:"allowGlobalUsageView"`
+
+	// 货币显示配置
+	CurrencyDisplay string `bun:"currency_display,notnull,default:'USD'" json:"currencyDisplay"`
+
+	// 计费模型来源配置: 'original' (重定向前) | 'redirected' (重定向后)
+	BillingModelSource string `bun:"billing_model_source,notnull,default:'original'" json:"billingModelSource"`
+
+	// 日志清理配置
+	EnableAutoCleanup    bool   `bun:"enable_auto_cleanup,default:false" json:"enableAutoCleanup"`
+	CleanupRetentionDays *int   `bun:"cleanup_retention_days,default:30" json:"cleanupRetentionDays"`
+	CleanupSchedule      string `bun:"cleanup_schedule,default:'0 2 * * *'" json:"cleanupSchedule"`
+	CleanupBatchSize     *int   `bun:"cleanup_batch_size,default:10000" json:"cleanupBatchSize"`
+
+	// 客户端版本检查配置
+	EnableClientVersionCheck bool `bun:"enable_client_version_check,notnull,default:false" json:"enableClientVersionCheck"`
+
+	// 供应商不可用时是否返回详细错误信息
+	VerboseProviderError bool `bun:"verbose_provider_error,notnull,default:false" json:"verboseProviderError"`
+
+	// 启用 HTTP/2 连接供应商(默认关闭,启用后自动回退到 HTTP/1.1 失败时)
+	EnableHttp2 bool `bun:"enable_http2,notnull,default:false" json:"enableHttp2"`
+
+	// 可选拦截 Anthropic Warmup 请求(默认关闭)
+	// 开启后:对 /v1/messages 的 Warmup 请求直接由 CCH 抢答,避免打到上游供应商
+	InterceptAnthropicWarmupRequests bool `bun:"intercept_anthropic_warmup_requests,notnull,default:false" json:"interceptAnthropicWarmupRequests"`
+
+	CreatedAt time.Time `bun:"created_at,notnull,default:current_timestamp" json:"createdAt"`
+	UpdatedAt time.Time `bun:"updated_at,notnull,default:current_timestamp" json:"updatedAt"`
+}
+
+// GetCleanupRetentionDays 获取日志保留天数
+func (s *SystemSettings) GetCleanupRetentionDays() int {
+	if s.CleanupRetentionDays == nil {
+		return 30
+	}
+	return *s.CleanupRetentionDays
+}
+
+// GetCleanupBatchSize 获取清理批次大小
+func (s *SystemSettings) GetCleanupBatchSize() int {
+	if s.CleanupBatchSize == nil {
+		return 10000
+	}
+	return *s.CleanupBatchSize
+}

+ 1 - 1
internal/model/user.go

@@ -14,7 +14,7 @@ type User struct {
 	ID          int      `bun:"id,pk,autoincrement" json:"id"`
 	Name        string   `bun:"name,notnull" json:"name"`
 	Description *string  `bun:"description" json:"description"`
-	Role        string   `bun:"role,notnull,default:'user'" json:"role"` // admin, user
+	Role        string   `bun:"role,default:'user'" json:"role"` // admin, user
 	Tags        []string `bun:"tags,type:jsonb" json:"tags"`
 
 	// 供应商组

+ 67 - 0
internal/model/webhook_target.go

@@ -0,0 +1,67 @@
+package model
+
+import (
+	"time"
+
+	"github.com/uptrace/bun"
+)
+
+// WebhookTestResult Webhook 测试结果
+type WebhookTestResult struct {
+	Success   bool    `json:"success"`
+	Message   *string `json:"message,omitempty"`
+	Timestamp string  `json:"timestamp,omitempty"`
+}
+
+// WebhookTarget Webhook 目标模型
+type WebhookTarget struct {
+	bun.BaseModel `bun:"table:webhook_targets,alias:wt"`
+
+	ID           int    `bun:"id,pk,autoincrement" json:"id"`
+	Name         string `bun:"name,notnull" json:"name"`
+	ProviderType string `bun:"provider_type,notnull" json:"providerType"` // wechat, feishu, dingtalk, telegram, custom
+
+	// 通用配置
+	WebhookUrl *string `bun:"webhook_url" json:"webhookUrl"`
+
+	// Telegram 特有配置
+	TelegramBotToken *string `bun:"telegram_bot_token" json:"telegramBotToken"`
+	TelegramChatId   *string `bun:"telegram_chat_id" json:"telegramChatId"`
+
+	// 钉钉签名配置
+	DingtalkSecret *string `bun:"dingtalk_secret" json:"dingtalkSecret"`
+
+	// 自定义 Webhook 配置
+	CustomTemplate map[string]interface{} `bun:"custom_template,type:jsonb" json:"customTemplate"`
+	CustomHeaders  map[string]interface{} `bun:"custom_headers,type:jsonb" json:"customHeaders"`
+
+	// 代理配置
+	ProxyUrl              *string `bun:"proxy_url" json:"proxyUrl"`
+	ProxyFallbackToDirect bool    `bun:"proxy_fallback_to_direct,default:false" json:"proxyFallbackToDirect"`
+
+	// 元数据
+	IsEnabled      bool               `bun:"is_enabled,notnull,default:true" json:"isEnabled"`
+	LastTestAt     *time.Time         `bun:"last_test_at" json:"lastTestAt"`
+	LastTestResult *WebhookTestResult `bun:"last_test_result,type:jsonb" json:"lastTestResult"`
+
+	CreatedAt time.Time `bun:"created_at,notnull,default:current_timestamp" json:"createdAt"`
+	UpdatedAt time.Time `bun:"updated_at,notnull,default:current_timestamp" json:"updatedAt"`
+
+	// 关联
+	Bindings []NotificationTargetBinding `bun:"rel:has-many,join:id=target_id" json:"bindings,omitempty"`
+}
+
+// IsActive 检查 Webhook 目标是否处于活跃状态
+func (w *WebhookTarget) IsActive() bool {
+	return w.IsEnabled
+}
+
+// IsTelegram 检查是否为 Telegram 类型
+func (w *WebhookTarget) IsTelegram() bool {
+	return w.ProviderType == string(WebhookProviderTypeTelegram)
+}
+
+// IsCustom 检查是否为自定义类型
+func (w *WebhookTarget) IsCustom() bool {
+	return w.ProviderType == string(WebhookProviderTypeCustom)
+}

+ 377 - 11
internal/pkg/errors/errors.go

@@ -12,38 +12,64 @@ const (
 	ErrorTypeInvalidRequest     ErrorType = "invalid_request"
 	ErrorTypeAuthentication     ErrorType = "authentication_error"
 	ErrorTypePermissionDenied   ErrorType = "permission_denied"
-	ErrorTypeRateLimitExceeded  ErrorType = "rate_limit_exceeded"
+	ErrorTypeRateLimitError     ErrorType = "rate_limit_error" // 与 Node.js 版本一致
 	ErrorTypeProviderError      ErrorType = "provider_error"
 	ErrorTypeCircuitBreakerOpen ErrorType = "circuit_breaker_open"
 	ErrorTypeInternal           ErrorType = "internal_error"
 	ErrorTypeNotFound           ErrorType = "not_found"
 )
 
+// ErrorCategory 错误分类 - 用于区分错误处理策略
+type ErrorCategory int
+
+const (
+	// CategoryProviderError 供应商问题(所有 4xx/5xx HTTP 错误)→ 计入熔断器 + 直接切换
+	CategoryProviderError ErrorCategory = iota
+	// CategorySystemError 系统/网络问题(fetch 网络异常)→ 不计入熔断器 + 先重试1次
+	CategorySystemError
+	// CategoryClientAbort 客户端主动中断 → 不计入熔断器 + 不重试 + 直接返回
+	CategoryClientAbort
+	// CategoryNonRetryableClientError 客户端输入错误 → 不计入熔断器 + 不重试 + 直接返回
+	CategoryNonRetryableClientError
+	// CategoryResourceNotFound 上游 404 错误 → 不计入熔断器 + 直接切换供应商
+	CategoryResourceNotFound
+)
+
 // ErrorCode 错误码
 type ErrorCode string
 
 const (
 	// 认证错误
-	CodeInvalidAPIKey  ErrorCode = "invalid_api_key"
-	CodeExpiredAPIKey  ErrorCode = "expired_api_key"
-	CodeDisabledAPIKey ErrorCode = "disabled_api_key"
-	CodeDisabledUser   ErrorCode = "disabled_user"
+	CodeInvalidAPIKey      ErrorCode = "invalid_api_key"
+	CodeExpiredAPIKey      ErrorCode = "expired_api_key"
+	CodeDisabledAPIKey     ErrorCode = "disabled_api_key"
+	CodeDisabledUser       ErrorCode = "disabled_user"
+	CodeUnauthorized       ErrorCode = "unauthorized"
+	CodeInvalidToken       ErrorCode = "invalid_token"
+	CodeTokenRequired      ErrorCode = "token_required"
+	CodeInvalidCredentials ErrorCode = "invalid_credentials"
+	CodeSessionExpired     ErrorCode = "session_expired"
 
 	// 权限错误
 	CodeModelNotAllowed  ErrorCode = "model_not_allowed"
 	CodeClientNotAllowed ErrorCode = "client_not_allowed"
+	CodePermissionDenied ErrorCode = "permission_denied"
 
 	// 限流错误
-	CodeRPMLimitExceeded     ErrorCode = "rpm_limit_exceeded"
-	CodeDailyLimitExceeded   ErrorCode = "daily_limit_exceeded"
-	CodeWeeklyLimitExceeded  ErrorCode = "weekly_limit_exceeded"
-	CodeMonthlyLimitExceeded ErrorCode = "monthly_limit_exceeded"
-	CodeTotalLimitExceeded   ErrorCode = "total_limit_exceeded"
+	CodeRateLimitExceeded          ErrorCode = "rate_limit_exceeded"
+	CodeRPMLimitExceeded           ErrorCode = "rpm_limit_exceeded"
+	Code5HLimitExceeded            ErrorCode = "5h_limit_exceeded"
+	CodeDailyLimitExceeded         ErrorCode = "daily_limit_exceeded"
+	CodeWeeklyLimitExceeded        ErrorCode = "weekly_limit_exceeded"
+	CodeMonthlyLimitExceeded       ErrorCode = "monthly_limit_exceeded"
+	CodeTotalLimitExceeded         ErrorCode = "total_limit_exceeded"
+	CodeConcurrentSessionsExceeded ErrorCode = "concurrent_sessions_exceeded"
 
 	// 供应商错误
 	CodeNoProviderAvailable ErrorCode = "no_provider_available"
 	CodeProviderTimeout     ErrorCode = "provider_timeout"
 	CodeProviderError       ErrorCode = "provider_error"
+	CodeEmptyResponse       ErrorCode = "empty_response"
 
 	// 熔断错误
 	CodeCircuitOpen ErrorCode = "circuit_open"
@@ -56,6 +82,43 @@ const (
 	// 请求错误
 	CodeInvalidRequest ErrorCode = "invalid_request"
 	CodeNotFound       ErrorCode = "not_found"
+
+	// 验证错误
+	CodeRequiredField    ErrorCode = "required_field"
+	CodeUserNameRequired ErrorCode = "user_name_required"
+	CodeAPIKeyRequired   ErrorCode = "api_key_required"
+	CodeProviderName     ErrorCode = "provider_name_required"
+	CodeProviderURL      ErrorCode = "provider_url_required"
+	CodeMinLength        ErrorCode = "min_length"
+	CodeMaxLength        ErrorCode = "max_length"
+	CodeMinValue         ErrorCode = "min_value"
+	CodeMaxValue         ErrorCode = "max_value"
+	CodeMustBeInteger    ErrorCode = "must_be_integer"
+	CodeMustBePositive   ErrorCode = "must_be_positive"
+	CodeInvalidEmail     ErrorCode = "invalid_email"
+	CodeInvalidURL       ErrorCode = "invalid_url"
+	CodeInvalidType      ErrorCode = "invalid_type"
+	CodeInvalidFormat    ErrorCode = "invalid_format"
+	CodeDuplicateName    ErrorCode = "duplicate_name"
+	CodeInvalidRange     ErrorCode = "invalid_range"
+	CodeEmptyUpdate      ErrorCode = "empty_update"
+
+	// 网络错误
+	CodeConnectionFailed ErrorCode = "connection_failed"
+	CodeTimeout          ErrorCode = "timeout"
+	CodeNetworkError     ErrorCode = "network_error"
+
+	// 业务错误
+	CodeQuotaExceeded ErrorCode = "quota_exceeded"
+	CodeResourceBusy  ErrorCode = "resource_busy"
+	CodeInvalidState  ErrorCode = "invalid_state"
+	CodeConflict      ErrorCode = "conflict"
+
+	// 操作错误
+	CodeOperationFailed ErrorCode = "operation_failed"
+	CodeCreateFailed    ErrorCode = "create_failed"
+	CodeUpdateFailed    ErrorCode = "update_failed"
+	CodeDeleteFailed    ErrorCode = "delete_failed"
 )
 
 // AppError 应用错误
@@ -153,7 +216,7 @@ func NewPermissionDenied(message string, code ErrorCode) *AppError {
 // NewRateLimitExceeded 创建限流错误
 func NewRateLimitExceeded(message string, code ErrorCode) *AppError {
 	return &AppError{
-		Type:       ErrorTypeRateLimitExceeded,
+		Type:       ErrorTypeRateLimitError,
 		Message:    message,
 		Code:       code,
 		HTTPStatus: http.StatusTooManyRequests,
@@ -237,3 +300,306 @@ func IsCode(err error, code ErrorCode) bool {
 	}
 	return false
 }
+
+// ============================================================================
+// 代理模块专用错误类型 - 与 Node.js 版本 src/app/v1/_lib/proxy/errors.ts 对齐
+// ============================================================================
+
+// LimitType 限流类型
+type LimitType string
+
+const (
+	LimitTypeRPM                LimitType = "rpm"
+	LimitTypeUSD5H              LimitType = "usd_5h"
+	LimitTypeUSDWeekly          LimitType = "usd_weekly"
+	LimitTypeUSDMonthly         LimitType = "usd_monthly"
+	LimitTypeUSDTotal           LimitType = "usd_total"
+	LimitTypeConcurrentSessions LimitType = "concurrent_sessions"
+	LimitTypeDailyQuota         LimitType = "daily_quota"
+)
+
+// RateLimitError 限流错误 - 携带详细的限流上下文信息
+// 与 Node.js 版本的 RateLimitError 类对齐
+type RateLimitError struct {
+	Type         string    `json:"type"`          // 固定为 "rate_limit_error"
+	Message      string    `json:"message"`       // 人类可读的错误消息
+	LimitType    LimitType `json:"limit_type"`    // 限流类型
+	CurrentUsage float64   `json:"current_usage"` // 当前使用量
+	LimitValue   float64   `json:"limit_value"`   // 限制值
+	ResetTime    string    `json:"reset_time"`    // 重置时间(ISO 8601 格式)
+	ProviderID   *int      `json:"provider_id"`   // 供应商 ID(可选)
+}
+
+// Error 实现 error 接口
+func (e *RateLimitError) Error() string {
+	return e.Message
+}
+
+// NewRateLimitError 创建限流错误
+func NewRateLimitError(
+	message string,
+	limitType LimitType,
+	currentUsage float64,
+	limitValue float64,
+	resetTime string,
+	providerID *int,
+) *RateLimitError {
+	return &RateLimitError{
+		Type:         "rate_limit_error",
+		Message:      message,
+		LimitType:    limitType,
+		CurrentUsage: currentUsage,
+		LimitValue:   limitValue,
+		ResetTime:    resetTime,
+		ProviderID:   providerID,
+	}
+}
+
+// ToJSON 获取适合记录到数据库的 JSON 元数据
+func (e *RateLimitError) ToJSON() map[string]interface{} {
+	return map[string]interface{}{
+		"type":          e.Type,
+		"limit_type":    e.LimitType,
+		"current_usage": e.CurrentUsage,
+		"limit_value":   e.LimitValue,
+		"reset_time":    e.ResetTime,
+		"provider_id":   e.ProviderID,
+		"message":       e.Message,
+	}
+}
+
+// IsRateLimitError 类型守卫:检查是否为 RateLimitError
+func IsRateLimitError(err error) bool {
+	_, ok := err.(*RateLimitError)
+	return ok
+}
+
+// AsRateLimitError 类型转换:将 error 转换为 RateLimitError
+func AsRateLimitError(err error) (*RateLimitError, bool) {
+	e, ok := err.(*RateLimitError)
+	return e, ok
+}
+
+// UpstreamError 上游错误信息
+type UpstreamError struct {
+	Body         string      `json:"body"`                    // 原始响应体(智能截断)
+	Parsed       interface{} `json:"parsed,omitempty"`        // 解析后的 JSON(如果有)
+	ProviderID   *int        `json:"provider_id,omitempty"`   // 供应商 ID
+	ProviderName string      `json:"provider_name,omitempty"` // 供应商名称
+	RequestID    string      `json:"request_id,omitempty"`    // 上游请求 ID
+}
+
+// ProxyError 代理错误 - 携带上游完整错误信息
+// 与 Node.js 版本的 ProxyError 类对齐
+type ProxyError struct {
+	Message       string         `json:"message"`
+	StatusCode    int            `json:"status_code"`
+	UpstreamError *UpstreamError `json:"upstream_error,omitempty"`
+}
+
+// Error 实现 error 接口
+func (e *ProxyError) Error() string {
+	return e.Message
+}
+
+// NewProxyError 创建代理错误
+func NewProxyError(message string, statusCode int, upstreamError *UpstreamError) *ProxyError {
+	return &ProxyError{
+		Message:       message,
+		StatusCode:    statusCode,
+		UpstreamError: upstreamError,
+	}
+}
+
+// GetDetailedErrorMessage 获取适合记录到数据库的详细错误信息
+// 格式:Provider {name} returned {status}: {message} | Upstream: {body}
+func (e *ProxyError) GetDetailedErrorMessage() string {
+	if e.UpstreamError != nil && e.UpstreamError.ProviderName != "" {
+		msg := fmt.Sprintf("Provider %s returned %d: %s",
+			e.UpstreamError.ProviderName, e.StatusCode, e.Message)
+		if e.UpstreamError.Body != "" {
+			msg += " | Upstream: " + e.UpstreamError.Body
+		}
+		return msg
+	}
+	return e.Message
+}
+
+// GetClientSafeMessage 获取适合返回给客户端的安全错误信息
+// 不包含供应商名称等敏感信息
+func (e *ProxyError) GetClientSafeMessage() string {
+	return e.Message
+}
+
+// IsProxyError 类型守卫:检查是否为 ProxyError
+func IsProxyError(err error) bool {
+	_, ok := err.(*ProxyError)
+	return ok
+}
+
+// AsProxyError 类型转换:将 error 转换为 ProxyError
+func AsProxyError(err error) (*ProxyError, bool) {
+	e, ok := err.(*ProxyError)
+	return e, ok
+}
+
+// EmptyResponseReason 空响应原因
+type EmptyResponseReason string
+
+const (
+	EmptyResponseReasonEmptyBody      EmptyResponseReason = "empty_body"
+	EmptyResponseReasonNoOutputTokens EmptyResponseReason = "no_output_tokens"
+	EmptyResponseReasonMissingContent EmptyResponseReason = "missing_content"
+)
+
+// EmptyResponseError 空响应错误 - 用于检测上游返回空响应或缺少输出 token 的情况
+// 与 Node.js 版本的 EmptyResponseError 类对齐
+type EmptyResponseError struct {
+	ProviderID   int                 `json:"provider_id"`
+	ProviderName string              `json:"provider_name"`
+	Reason       EmptyResponseReason `json:"reason"`
+	message      string
+}
+
+// Error 实现 error 接口
+func (e *EmptyResponseError) Error() string {
+	return e.message
+}
+
+// NewEmptyResponseError 创建空响应错误
+func NewEmptyResponseError(providerID int, providerName string, reason EmptyResponseReason) *EmptyResponseError {
+	reasonMessages := map[EmptyResponseReason]string{
+		EmptyResponseReasonEmptyBody:      "Response body is empty",
+		EmptyResponseReasonNoOutputTokens: "Response has no output tokens",
+		EmptyResponseReasonMissingContent: "Response is missing content field",
+	}
+	message := fmt.Sprintf("Empty response from provider %s: %s", providerName, reasonMessages[reason])
+	return &EmptyResponseError{
+		ProviderID:   providerID,
+		ProviderName: providerName,
+		Reason:       reason,
+		message:      message,
+	}
+}
+
+// ToJSON 获取适合记录的 JSON 元数据
+func (e *EmptyResponseError) ToJSON() map[string]interface{} {
+	return map[string]interface{}{
+		"type":          "empty_response_error",
+		"provider_id":   e.ProviderID,
+		"provider_name": e.ProviderName,
+		"reason":        e.Reason,
+		"message":       e.message,
+	}
+}
+
+// GetClientSafeMessage 获取适合返回给客户端的安全错误信息
+// 不包含供应商名称等敏感信息
+func (e *EmptyResponseError) GetClientSafeMessage() string {
+	reasonMessages := map[EmptyResponseReason]string{
+		EmptyResponseReasonEmptyBody:      "Response body is empty",
+		EmptyResponseReasonNoOutputTokens: "Response has no output tokens",
+		EmptyResponseReasonMissingContent: "Response is missing content field",
+	}
+	return fmt.Sprintf("Empty response: %s", reasonMessages[e.Reason])
+}
+
+// IsEmptyResponseError 类型守卫:检查是否为 EmptyResponseError
+func IsEmptyResponseError(err error) bool {
+	_, ok := err.(*EmptyResponseError)
+	return ok
+}
+
+// AsEmptyResponseError 类型转换:将 error 转换为 EmptyResponseError
+func AsEmptyResponseError(err error) (*EmptyResponseError, bool) {
+	e, ok := err.(*EmptyResponseError)
+	return e, ok
+}
+
+// ============================================================================
+// 错误分类函数 - 与 Node.js 版本 categorizeErrorAsync 对齐
+// ============================================================================
+
+// CategorizeError 判断错误类型
+// 分类规则(优先级从高到低):
+// 1. 客户端主动中断 → CategoryClientAbort
+// 2. 不可重试的客户端输入错误 → CategoryNonRetryableClientError
+// 3. ProxyError 404 → CategoryResourceNotFound
+// 4. ProxyError 其他 → CategoryProviderError
+// 5. EmptyResponseError → CategoryProviderError
+// 6. 其他 → CategorySystemError
+func CategorizeError(err error) ErrorCategory {
+	// 优先级 1: 客户端中断检测
+	if IsClientAbortError(err) {
+		return CategoryClientAbort
+	}
+
+	// 优先级 2: 不可重试的客户端输入错误(需要配合错误规则检测器)
+	// 注意:这里需要外部调用者提供规则检测结果
+	// Go 版本中可以通过 context 或者单独的检测函数实现
+
+	// 优先级 3: ProxyError
+	if proxyErr, ok := AsProxyError(err); ok {
+		if proxyErr.StatusCode == http.StatusNotFound {
+			return CategoryResourceNotFound
+		}
+		return CategoryProviderError
+	}
+
+	// 优先级 4: EmptyResponseError
+	if IsEmptyResponseError(err) {
+		return CategoryProviderError
+	}
+
+	// 优先级 5: 其他都是系统错误
+	return CategorySystemError
+}
+
+// IsClientAbortError 检测是否为客户端中断错误
+// 采用白名单模式,精确检测客户端主动中断的错误
+func IsClientAbortError(err error) bool {
+	if err == nil {
+		return false
+	}
+
+	// 检查 ProxyError 状态码 499(Client Closed Request)
+	if proxyErr, ok := AsProxyError(err); ok {
+		if proxyErr.StatusCode == 499 {
+			return true
+		}
+	}
+
+	// 检查错误消息中的中断标识
+	errMsg := err.Error()
+	abortMessages := []string{
+		"context canceled",
+		"context deadline exceeded",
+		"client disconnected",
+		"connection reset by peer",
+	}
+
+	for _, msg := range abortMessages {
+		if contains(errMsg, msg) {
+			return true
+		}
+	}
+
+	return false
+}
+
+// contains 检查字符串是否包含子串(不区分大小写)
+func contains(s, substr string) bool {
+	return len(s) >= len(substr) &&
+		(s == substr ||
+			len(s) > len(substr) && findSubstring(s, substr))
+}
+
+// findSubstring 简单的子串查找
+func findSubstring(s, substr string) bool {
+	for i := 0; i <= len(s)-len(substr); i++ {
+		if s[i:i+len(substr)] == substr {
+			return true
+		}
+	}
+	return false
+}

+ 139 - 13
internal/pkg/logger/logger.go

@@ -3,53 +3,179 @@ package logger
 import (
 	"io"
 	"os"
+	"strings"
+	"sync"
 	"time"
 
 	"github.com/rs/zerolog"
 )
 
+// LogLevel 日志级别类型
+type LogLevel string
+
+const (
+	LevelFatal LogLevel = "fatal"
+	LevelError LogLevel = "error"
+	LevelWarn  LogLevel = "warn"
+	LevelInfo  LogLevel = "info"
+	LevelDebug LogLevel = "debug"
+	LevelTrace LogLevel = "trace"
+)
+
+// levelPriority 日志级别优先级(与 Node.js 版本一致)
+var levelPriority = map[LogLevel]int{
+	LevelTrace: 10,
+	LevelDebug: 20,
+	LevelInfo:  30,
+	LevelWarn:  40,
+	LevelError: 50,
+	LevelFatal: 60,
+}
+
+// validLevels 有效的日志级别列表
+var validLevels = []LogLevel{LevelFatal, LevelError, LevelWarn, LevelInfo, LevelDebug, LevelTrace}
+
 var (
 	// Log 全局日志实例
 	Log zerolog.Logger
+	// currentLevel 当前日志级别
+	currentLevel LogLevel
+	// mu 保护日志级别的互斥锁
+	mu sync.RWMutex
 )
 
 // Config 日志配置
 type Config struct {
-	Level  string // debug, info, warn, error
-	Format string // json, text
+	Level       string // fatal, error, warn, info, debug, trace
+	Format      string // json, text
+	Development bool   // 是否为开发环境
+	DebugMode   bool   // 向后兼容:DEBUG_MODE 环境变量
+}
+
+// isValidLevel 检查日志级别是否有效
+func isValidLevel(level string) bool {
+	for _, l := range validLevels {
+		if string(l) == level {
+			return true
+		}
+	}
+	return false
+}
+
+// getInitialLogLevel 获取初始日志级别
+// - 优先使用配置的 Level
+// - 向后兼容:如果设置了 DebugMode,使用 debug 级别
+// - 开发环境默认 debug
+// - 生产环境默认 info
+func getInitialLogLevel(cfg Config) LogLevel {
+	// 优先使用配置的日志级别
+	envLevel := strings.ToLower(cfg.Level)
+	if envLevel != "" && isValidLevel(envLevel) {
+		return LogLevel(envLevel)
+	}
+
+	// 向后兼容:如果设置了 DEBUG_MODE,使用 debug 级别
+	if cfg.DebugMode {
+		return LevelDebug
+	}
+
+	// 开发环境默认 debug,生产环境默认 info
+	if cfg.Development {
+		return LevelDebug
+	}
+	return LevelInfo
+}
+
+// logLevelToZerolog 将 LogLevel 转换为 zerolog.Level
+func logLevelToZerolog(level LogLevel) zerolog.Level {
+	switch level {
+	case LevelTrace:
+		return zerolog.TraceLevel
+	case LevelDebug:
+		return zerolog.DebugLevel
+	case LevelInfo:
+		return zerolog.InfoLevel
+	case LevelWarn:
+		return zerolog.WarnLevel
+	case LevelError:
+		return zerolog.ErrorLevel
+	case LevelFatal:
+		return zerolog.FatalLevel
+	default:
+		return zerolog.InfoLevel
+	}
 }
 
 // Init 初始化日志
 func Init(cfg Config) {
-	// 设置时间格式
-	zerolog.TimeFieldFormat = time.RFC3339Nano
+	// 获取初始日志级别
+	initialLevel := getInitialLogLevel(cfg)
+	mu.Lock()
+	currentLevel = initialLevel
+	mu.Unlock()
 
-	// 解析日志级别
-	level, err := zerolog.ParseLevel(cfg.Level)
-	if err != nil {
-		level = zerolog.InfoLevel
-	}
-	zerolog.SetGlobalLevel(level)
+	// 设置 zerolog 全局级别
+	zerolog.SetGlobalLevel(logLevelToZerolog(initialLevel))
 
 	// 选择输出格式
 	var writer io.Writer
-	if cfg.Format == "text" {
+
+	// 开发环境使用美化输出(类似 pino-pretty)
+	if cfg.Format == "text" || cfg.Development {
 		writer = zerolog.ConsoleWriter{
 			Out:        os.Stdout,
-			TimeFormat: "2006-01-02 15:04:05.000",
+			TimeFormat: "2006-01-02 15:04:05", // 类似 pino-pretty 的 SYS:standard
+			NoColor:    false,                 // 启用颜色(colorize: true)
+			// 格式化函数:level 输出为标签字符串
+			FormatLevel: func(i interface{}) string {
+				if ll, ok := i.(string); ok {
+					return strings.ToUpper(ll)
+				}
+				return "???"
+			},
 		}
 	} else {
+		// 生产环境使用 JSON 格式
 		writer = os.Stdout
 	}
 
+	// 设置时间格式为 ISO 8601(与 pino 的 stdTimeFunctions.isoTime 一致)
+	zerolog.TimeFieldFormat = time.RFC3339
+
 	// 创建日志实例
+	// 注意:不添加 pid 和 hostname(与 Node.js 版本一致:ignore: "pid,hostname")
 	Log = zerolog.New(writer).
 		With().
 		Timestamp().
-		Caller().
 		Logger()
 }
 
+// SetLogLevel 运行时动态调整日志级别
+func SetLogLevel(newLevel LogLevel) {
+	if !isValidLevel(string(newLevel)) {
+		return
+	}
+
+	mu.Lock()
+	currentLevel = newLevel
+	mu.Unlock()
+
+	zerolog.SetGlobalLevel(logLevelToZerolog(newLevel))
+	Log.Info().Msgf("日志级别已调整为: %s", newLevel)
+}
+
+// GetLogLevel 获取当前日志级别
+func GetLogLevel() LogLevel {
+	mu.RLock()
+	defer mu.RUnlock()
+	return currentLevel
+}
+
+// Trace 返回 trace 级别的事件
+func Trace() *zerolog.Event {
+	return Log.Trace()
+}
+
 // Debug 返回 debug 级别的事件
 func Debug() *zerolog.Event {
 	return Log.Debug()