2026-04-27 23:20:24 +08:00
|
|
|
|
package config
|
2026-03-09 21:28:58 +08:00
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
|
"fmt"
|
|
|
|
|
|
"strings"
|
|
|
|
|
|
"time"
|
|
|
|
|
|
|
|
|
|
|
|
"github.com/spf13/viper"
|
|
|
|
|
|
)
|
|
|
|
|
|
|
2026-03-13 09:38:18 +08:00
|
|
|
|
// Config 主配置结构体
|
2026-03-09 21:28:58 +08:00
|
|
|
|
type Config struct {
|
2026-03-12 08:38:14 +08:00
|
|
|
|
Server ServerConfig `mapstructure:"server"`
|
|
|
|
|
|
Database DatabaseConfig `mapstructure:"database"`
|
|
|
|
|
|
Redis RedisConfig `mapstructure:"redis"`
|
|
|
|
|
|
Cache CacheConfig `mapstructure:"cache"`
|
|
|
|
|
|
S3 S3Config `mapstructure:"s3"`
|
|
|
|
|
|
JWT JWTConfig `mapstructure:"jwt"`
|
|
|
|
|
|
Log LogConfig `mapstructure:"log"`
|
|
|
|
|
|
RateLimit RateLimitConfig `mapstructure:"rate_limit"`
|
|
|
|
|
|
Upload UploadConfig `mapstructure:"upload"`
|
2026-06-17 20:41:55 +08:00
|
|
|
|
FileCleanup FileCleanupConfig `mapstructure:"file_cleanup"`
|
2026-06-18 20:34:22 +08:00
|
|
|
|
DeviceCleanup DeviceCleanupConfig `mapstructure:"device_cleanup"`
|
2026-03-12 08:38:14 +08:00
|
|
|
|
OpenAI OpenAIConfig `mapstructure:"openai"`
|
2026-04-21 22:31:30 +08:00
|
|
|
|
TencentTMS TencentTMSConfig `mapstructure:"tencent_tms"`
|
2026-03-12 08:38:14 +08:00
|
|
|
|
Email EmailConfig `mapstructure:"email"`
|
|
|
|
|
|
ConversationCache ConversationCacheConfig `mapstructure:"conversation_cache"`
|
2026-03-13 20:40:20 +08:00
|
|
|
|
GRPC GRPCConfig `mapstructure:"grpc"`
|
2026-03-14 02:09:38 +08:00
|
|
|
|
Casbin CasbinConfig `mapstructure:"casbin"`
|
2026-03-15 20:38:22 +08:00
|
|
|
|
Encryption EncryptionConfig `mapstructure:"encryption"`
|
2026-03-24 05:18:30 +08:00
|
|
|
|
HotRank HotRankConfig `mapstructure:"hot_rank"`
|
2026-06-01 13:41:02 +08:00
|
|
|
|
LiveKit LiveKitConfig `mapstructure:"livekit"`
|
2026-03-29 20:18:36 +08:00
|
|
|
|
Report ReportConfig `mapstructure:"report"`
|
2026-04-07 00:07:40 +08:00
|
|
|
|
Sensitive SensitiveConfig `mapstructure:"sensitive"`
|
2026-04-27 23:20:24 +08:00
|
|
|
|
JPush JPushConfig `mapstructure:"jpush"`
|
2026-04-24 15:14:44 +08:00
|
|
|
|
SetupSecret string `mapstructure:"setup_secret"`
|
2026-05-06 12:39:11 +08:00
|
|
|
|
WebSocket WSConfig `mapstructure:"websocket"`
|
2026-05-07 01:08:39 +08:00
|
|
|
|
Runner RunnerConfig `mapstructure:"runner"`
|
2026-05-17 23:38:04 +08:00
|
|
|
|
SeqBuffer SeqBufferConfig `mapstructure:"seq_buffer"`
|
|
|
|
|
|
VersionLog VersionLogConfig `mapstructure:"version_log"`
|
|
|
|
|
|
PushWorker PushWorkerConfig `mapstructure:"push_worker"`
|
2026-03-09 21:28:58 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-13 09:38:18 +08:00
|
|
|
|
// Load 加载配置文件
|
2026-03-09 21:28:58 +08:00
|
|
|
|
func Load(configPath string) (*Config, error) {
|
|
|
|
|
|
viper.SetConfigFile(configPath)
|
|
|
|
|
|
viper.SetConfigType("yaml")
|
|
|
|
|
|
|
|
|
|
|
|
// 启用环境变量支持
|
|
|
|
|
|
viper.SetEnvPrefix("APP")
|
|
|
|
|
|
viper.AutomaticEnv()
|
|
|
|
|
|
// 允许环境变量使用下划线或连字符
|
|
|
|
|
|
viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_", "-", "_"))
|
|
|
|
|
|
|
|
|
|
|
|
// Set default values
|
|
|
|
|
|
viper.SetDefault("server.port", 8080)
|
|
|
|
|
|
viper.SetDefault("server.mode", "debug")
|
|
|
|
|
|
viper.SetDefault("server.host", "0.0.0.0")
|
|
|
|
|
|
viper.SetDefault("database.type", "sqlite")
|
2026-04-22 16:01:59 +08:00
|
|
|
|
viper.SetDefault("database.sqlite.path", "./data/with_you.db")
|
2026-03-09 21:28:58 +08:00
|
|
|
|
viper.SetDefault("database.max_idle_conns", 10)
|
|
|
|
|
|
viper.SetDefault("database.max_open_conns", 100)
|
|
|
|
|
|
viper.SetDefault("database.log_level", "warn")
|
|
|
|
|
|
viper.SetDefault("database.slow_threshold_ms", 200)
|
|
|
|
|
|
viper.SetDefault("database.ignore_record_not_found", true)
|
|
|
|
|
|
viper.SetDefault("database.parameterized_queries", true)
|
2026-05-15 14:44:40 +08:00
|
|
|
|
viper.SetDefault("database.replica_policy", "random")
|
2026-06-18 02:01:57 +08:00
|
|
|
|
|
|
|
|
|
|
// 读写分离:单副本字段显式 BindEnv。
|
|
|
|
|
|
// AutomaticEnv 对 YAML 中未出现的嵌套 key(replica 段在 config.yaml 中默认被注释)
|
|
|
|
|
|
// 不会反向推导,必须逐字段 BindEnv 才能让 APP_DATABASE_REPLICA_HOST 等环境变量生效。
|
|
|
|
|
|
// 与 jpush.channel.* 处理方式一致(见下方 BindEnv 块)。
|
|
|
|
|
|
viper.BindEnv("database.replica.host", "APP_DATABASE_REPLICA_HOST")
|
|
|
|
|
|
viper.BindEnv("database.replica.port", "APP_DATABASE_REPLICA_PORT")
|
|
|
|
|
|
viper.BindEnv("database.replica.user", "APP_DATABASE_REPLICA_USER")
|
|
|
|
|
|
viper.BindEnv("database.replica.password", "APP_DATABASE_REPLICA_PASSWORD")
|
|
|
|
|
|
viper.BindEnv("database.replica.dbname", "APP_DATABASE_REPLICA_DBNAME")
|
|
|
|
|
|
viper.BindEnv("database.replica.sslmode", "APP_DATABASE_REPLICA_SSLMODE")
|
|
|
|
|
|
// 副本连接池参数(可选,留空回退到主库连接池配置,见 database.go)
|
|
|
|
|
|
viper.BindEnv("database.replica_max_idle_conns", "APP_DATABASE_REPLICA_MAX_IDLE_CONNS")
|
|
|
|
|
|
viper.BindEnv("database.replica_max_open_conns", "APP_DATABASE_REPLICA_MAX_OPEN_CONNS")
|
2026-03-09 21:28:58 +08:00
|
|
|
|
viper.SetDefault("redis.type", "miniredis")
|
|
|
|
|
|
viper.SetDefault("redis.redis.host", "localhost")
|
|
|
|
|
|
viper.SetDefault("redis.redis.port", 6379)
|
|
|
|
|
|
viper.SetDefault("redis.redis.password", "")
|
|
|
|
|
|
viper.SetDefault("redis.redis.db", 0)
|
|
|
|
|
|
viper.SetDefault("redis.miniredis.host", "localhost")
|
|
|
|
|
|
viper.SetDefault("redis.miniredis.port", 6379)
|
|
|
|
|
|
viper.SetDefault("redis.pool_size", 10)
|
|
|
|
|
|
viper.SetDefault("cache.enabled", true)
|
|
|
|
|
|
viper.SetDefault("cache.key_prefix", "")
|
|
|
|
|
|
viper.SetDefault("cache.default_ttl", 30)
|
|
|
|
|
|
viper.SetDefault("cache.null_ttl", 5)
|
|
|
|
|
|
viper.SetDefault("cache.jitter_ratio", 0.1)
|
|
|
|
|
|
viper.SetDefault("cache.disable_flushdb", true)
|
|
|
|
|
|
viper.SetDefault("cache.modules.post_list_ttl", 30)
|
|
|
|
|
|
viper.SetDefault("cache.modules.conversation_ttl", 60)
|
|
|
|
|
|
viper.SetDefault("cache.modules.unread_count_ttl", 30)
|
|
|
|
|
|
viper.SetDefault("cache.modules.group_members_ttl", 120)
|
2026-03-20 12:23:28 +08:00
|
|
|
|
viper.SetDefault("cache.local.enabled", true)
|
|
|
|
|
|
viper.SetDefault("cache.local.size", 10000)
|
|
|
|
|
|
viper.SetDefault("cache.local.buckets", 0)
|
|
|
|
|
|
viper.SetDefault("cache.local.default_ttl", 300)
|
2026-03-09 21:28:58 +08:00
|
|
|
|
viper.SetDefault("jwt.secret", "your-jwt-secret-key-change-in-production")
|
|
|
|
|
|
viper.SetDefault("jwt.access_token_expire", 86400)
|
|
|
|
|
|
viper.SetDefault("jwt.refresh_token_expire", 604800)
|
|
|
|
|
|
viper.SetDefault("log.level", "info")
|
|
|
|
|
|
viper.SetDefault("log.encoding", "json")
|
|
|
|
|
|
viper.SetDefault("log.output_paths", []string{"stdout", "./logs/app.log"})
|
|
|
|
|
|
viper.SetDefault("rate_limit.enabled", true)
|
|
|
|
|
|
viper.SetDefault("rate_limit.requests_per_minute", 60)
|
|
|
|
|
|
viper.SetDefault("upload.max_file_size", 10485760)
|
|
|
|
|
|
viper.SetDefault("upload.allowed_types", []string{"image/jpeg", "image/png", "image/gif", "image/webp"})
|
2026-06-17 20:41:55 +08:00
|
|
|
|
// 文件过期清理默认值
|
|
|
|
|
|
viper.SetDefault("file_cleanup.enabled", true)
|
|
|
|
|
|
viper.SetDefault("file_cleanup.retention_days", 7)
|
|
|
|
|
|
viper.SetDefault("file_cleanup.interval_minutes", 360)
|
|
|
|
|
|
viper.SetDefault("file_cleanup.batch_size", 100)
|
2026-06-18 20:34:22 +08:00
|
|
|
|
// 设备 registration_id 清理默认值(3 天不活跃即清理)
|
|
|
|
|
|
viper.SetDefault("device_cleanup.enabled", true)
|
|
|
|
|
|
viper.SetDefault("device_cleanup.retention_days", 3)
|
|
|
|
|
|
viper.SetDefault("device_cleanup.interval_minutes", 360)
|
2026-03-09 21:28:58 +08:00
|
|
|
|
viper.SetDefault("s3.endpoint", "")
|
|
|
|
|
|
viper.SetDefault("s3.access_key", "")
|
|
|
|
|
|
viper.SetDefault("s3.secret_key", "")
|
|
|
|
|
|
viper.SetDefault("s3.bucket", "")
|
|
|
|
|
|
viper.SetDefault("s3.use_ssl", true)
|
|
|
|
|
|
viper.SetDefault("s3.region", "us-east-1")
|
|
|
|
|
|
viper.SetDefault("s3.domain", "")
|
|
|
|
|
|
viper.SetDefault("sensitive.enabled", true)
|
|
|
|
|
|
viper.SetDefault("sensitive.replace_str", "***")
|
2026-04-07 00:07:40 +08:00
|
|
|
|
viper.SetDefault("sensitive.min_match_len", 2)
|
|
|
|
|
|
viper.SetDefault("sensitive.load_from_db", true)
|
|
|
|
|
|
viper.SetDefault("sensitive.load_from_redis", false)
|
|
|
|
|
|
viper.SetDefault("sensitive.redis_key_prefix", "sensitive_words")
|
2026-03-09 21:28:58 +08:00
|
|
|
|
viper.SetDefault("audit.enabled", false)
|
|
|
|
|
|
viper.SetDefault("audit.provider", "local")
|
|
|
|
|
|
viper.SetDefault("openai.enabled", true)
|
|
|
|
|
|
viper.SetDefault("openai.base_url", "https://api.littlelan.cn/")
|
|
|
|
|
|
viper.SetDefault("openai.api_key", "")
|
|
|
|
|
|
viper.SetDefault("openai.moderation_model", "qwen3.5-122b")
|
|
|
|
|
|
viper.SetDefault("openai.moderation_max_images_per_request", 1)
|
|
|
|
|
|
viper.SetDefault("openai.request_timeout", 30)
|
|
|
|
|
|
viper.SetDefault("openai.strict_moderation", false)
|
2026-04-21 22:31:30 +08:00
|
|
|
|
viper.SetDefault("tencent_tms.enabled", false)
|
|
|
|
|
|
viper.SetDefault("tencent_tms.secret_id", "")
|
|
|
|
|
|
viper.SetDefault("tencent_tms.secret_key", "")
|
|
|
|
|
|
viper.SetDefault("tencent_tms.region", "ap-guangzhou")
|
|
|
|
|
|
viper.SetDefault("tencent_tms.biz_type", "")
|
|
|
|
|
|
viper.SetDefault("tencent_tms.timeout", 30)
|
2026-03-09 21:28:58 +08:00
|
|
|
|
viper.SetDefault("email.enabled", false)
|
|
|
|
|
|
viper.SetDefault("email.host", "")
|
|
|
|
|
|
viper.SetDefault("email.port", 587)
|
|
|
|
|
|
viper.SetDefault("email.username", "")
|
|
|
|
|
|
viper.SetDefault("email.password", "")
|
|
|
|
|
|
viper.SetDefault("email.from_address", "")
|
2026-04-22 16:01:59 +08:00
|
|
|
|
viper.SetDefault("email.from_name", "WithYou")
|
2026-03-09 21:28:58 +08:00
|
|
|
|
viper.SetDefault("email.use_tls", true)
|
|
|
|
|
|
viper.SetDefault("email.insecure_skip_verify", false)
|
|
|
|
|
|
viper.SetDefault("email.timeout", 15)
|
2026-03-12 08:38:14 +08:00
|
|
|
|
// ConversationCache 默认值
|
|
|
|
|
|
viper.SetDefault("conversation_cache.detail_ttl", "5m")
|
|
|
|
|
|
viper.SetDefault("conversation_cache.list_ttl", "60s")
|
|
|
|
|
|
viper.SetDefault("conversation_cache.participant_ttl", "5m")
|
|
|
|
|
|
viper.SetDefault("conversation_cache.unread_ttl", "30s")
|
|
|
|
|
|
viper.SetDefault("conversation_cache.message_detail_ttl", "30m")
|
|
|
|
|
|
viper.SetDefault("conversation_cache.message_list_ttl", "5m")
|
|
|
|
|
|
viper.SetDefault("conversation_cache.message_index_ttl", "30m")
|
|
|
|
|
|
viper.SetDefault("conversation_cache.message_count_ttl", "30m")
|
|
|
|
|
|
viper.SetDefault("conversation_cache.batch_interval", "5s")
|
|
|
|
|
|
viper.SetDefault("conversation_cache.batch_threshold", 100)
|
|
|
|
|
|
viper.SetDefault("conversation_cache.batch_max_size", 500)
|
|
|
|
|
|
viper.SetDefault("conversation_cache.buffer_max_size", 10000)
|
2026-03-13 20:40:20 +08:00
|
|
|
|
// GRPC 默认值
|
|
|
|
|
|
viper.SetDefault("grpc.enabled", true)
|
|
|
|
|
|
viper.SetDefault("grpc.port", 50051)
|
|
|
|
|
|
viper.SetDefault("grpc.tls_enabled", false)
|
|
|
|
|
|
viper.SetDefault("grpc.tls_cert_file", "")
|
|
|
|
|
|
viper.SetDefault("grpc.tls_key_file", "")
|
2026-04-27 23:20:24 +08:00
|
|
|
|
viper.SetDefault("casbin.model_path", "./configs/casbin/model.conf")
|
|
|
|
|
|
viper.SetDefault("casbin.auto_save", true)
|
|
|
|
|
|
viper.SetDefault("casbin.auto_load", true)
|
|
|
|
|
|
viper.SetDefault("casbin.enable_cache", true)
|
|
|
|
|
|
viper.SetDefault("casbin.cache_ttl", 300)
|
|
|
|
|
|
viper.SetDefault("casbin.skip_routes", []string{"/health", "/api/v1/auth/login", "/api/v1/auth/register"})
|
2026-03-17 10:10:49 +08:00
|
|
|
|
// Encryption 默认值
|
|
|
|
|
|
viper.SetDefault("encryption.enabled", false)
|
|
|
|
|
|
viper.SetDefault("encryption.key", "")
|
|
|
|
|
|
viper.SetDefault("encryption.key_version", 1)
|
2026-03-24 05:18:30 +08:00
|
|
|
|
viper.SetDefault("hot_rank.enabled", true)
|
|
|
|
|
|
viper.SetDefault("hot_rank.refresh_interval_seconds", 300)
|
|
|
|
|
|
viper.SetDefault("hot_rank.top_n", 100)
|
|
|
|
|
|
viper.SetDefault("hot_rank.recent_window_hours", 168)
|
|
|
|
|
|
viper.SetDefault("hot_rank.recent_fetch_limit", 500)
|
|
|
|
|
|
viper.SetDefault("hot_rank.candidate_cap", 800)
|
2026-03-29 20:18:36 +08:00
|
|
|
|
// Report 默认值
|
|
|
|
|
|
viper.SetDefault("report.auto_hide_threshold", 3)
|
2026-04-27 23:20:24 +08:00
|
|
|
|
viper.SetDefault("jpush.enabled", false)
|
|
|
|
|
|
viper.SetDefault("jpush.app_key", "")
|
|
|
|
|
|
viper.SetDefault("jpush.master_secret", "")
|
|
|
|
|
|
viper.SetDefault("jpush.production", false)
|
2026-06-17 20:41:55 +08:00
|
|
|
|
// JPush 厂商通道 channel_id 默认值
|
|
|
|
|
|
// 各厂商 channel_id 通过环境变量配置(见下方 BindEnv)
|
|
|
|
|
|
viper.SetDefault("jpush.channel.system", "")
|
|
|
|
|
|
viper.SetDefault("jpush.channel.chat", "")
|
2026-06-18 19:41:32 +08:00
|
|
|
|
viper.SetDefault("jpush.channel.vendor.xiaomi.system", "153609")
|
|
|
|
|
|
viper.SetDefault("jpush.channel.vendor.xiaomi.chat", "153608")
|
2026-06-17 20:41:55 +08:00
|
|
|
|
viper.SetDefault("jpush.channel.vendor.huawei.system", "")
|
|
|
|
|
|
viper.SetDefault("jpush.channel.vendor.huawei.chat", "")
|
|
|
|
|
|
viper.SetDefault("jpush.channel.vendor.oppo.system", "")
|
|
|
|
|
|
viper.SetDefault("jpush.channel.vendor.oppo.chat", "")
|
|
|
|
|
|
viper.SetDefault("jpush.channel.vendor.vivo.system", "")
|
|
|
|
|
|
viper.SetDefault("jpush.channel.vendor.vivo.chat", "")
|
|
|
|
|
|
viper.SetDefault("jpush.channel.vendor.meizu.system", "")
|
|
|
|
|
|
viper.SetDefault("jpush.channel.vendor.meizu.chat", "")
|
|
|
|
|
|
viper.SetDefault("jpush.channel.vendor.honor.system", "")
|
|
|
|
|
|
viper.SetDefault("jpush.channel.vendor.honor.chat", "")
|
|
|
|
|
|
viper.SetDefault("jpush.channel.vendor.fcm.system", "")
|
|
|
|
|
|
viper.SetDefault("jpush.channel.vendor.fcm.chat", "")
|
|
|
|
|
|
// 小米/OPPO 模板 id 默认值
|
2026-06-18 19:41:32 +08:00
|
|
|
|
viper.SetDefault("jpush.channel.vendor.xiaomi.mi_system_template_id", "P10761")
|
|
|
|
|
|
viper.SetDefault("jpush.channel.vendor.xiaomi.mi_chat_template_id", "M10289")
|
2026-06-17 20:41:55 +08:00
|
|
|
|
viper.SetDefault("jpush.channel.vendor.oppo.oppo_system_private_template_id", "")
|
|
|
|
|
|
viper.SetDefault("jpush.channel.vendor.oppo.oppo_chat_private_template_id", "")
|
2026-06-18 19:41:32 +08:00
|
|
|
|
// OPPO 消息分类默认值(category / notify_level)
|
|
|
|
|
|
// OPPO 消息分类默认值(2024.11.20 新规)
|
|
|
|
|
|
// chat: category=IM(即时通讯), notify_level=16(通知栏+锁屏+横幅+震动+铃声,强提醒)
|
|
|
|
|
|
// system: category=ACCOUNT(账号动态/互动通知), notify_level=2(通知栏+锁屏,避免打扰)
|
|
|
|
|
|
// 注:使用 notify_level 时 category 必传,此处两者同时配置
|
|
|
|
|
|
viper.SetDefault("jpush.channel.vendor.oppo.oppo_system_category", "ACCOUNT")
|
|
|
|
|
|
viper.SetDefault("jpush.channel.vendor.oppo.oppo_chat_category", "IM")
|
|
|
|
|
|
viper.SetDefault("jpush.channel.vendor.oppo.oppo_system_notify_level", 2)
|
|
|
|
|
|
viper.SetDefault("jpush.channel.vendor.oppo.oppo_chat_notify_level", 16)
|
|
|
|
|
|
// 荣耀 importance 默认值
|
|
|
|
|
|
// chat: NORMAL(服务与通讯,及时送达), system: LOW(资讯营销,避免打扰)
|
|
|
|
|
|
// 注:classification 优先级更高,会覆盖 importance
|
|
|
|
|
|
viper.SetDefault("jpush.channel.vendor.honor.honor_system_importance", "LOW")
|
|
|
|
|
|
viper.SetDefault("jpush.channel.vendor.honor.honor_chat_importance", "NORMAL")
|
|
|
|
|
|
// vivo category 默认值(classification=1 时必须为系统消息类)
|
|
|
|
|
|
// chat: IM(即时通讯), system: ACCOUNT(账号动态/互动通知)
|
|
|
|
|
|
viper.SetDefault("jpush.channel.vendor.vivo.vivo_system_category", "ACCOUNT")
|
|
|
|
|
|
viper.SetDefault("jpush.channel.vendor.vivo.vivo_chat_category", "IM")
|
|
|
|
|
|
// iOS 通知分组 thread-id 默认值(留空,不分组)
|
2026-06-17 20:41:55 +08:00
|
|
|
|
viper.SetDefault("jpush.channel.ios.chat_thread_id", "")
|
|
|
|
|
|
viper.SetDefault("jpush.channel.ios.system_thread_id", "")
|
|
|
|
|
|
// JPush 厂商 channel_id 环境变量显式绑定
|
|
|
|
|
|
// AutomaticEnv 对深层嵌套 key 不可靠,故逐个 BindEnv
|
|
|
|
|
|
// 命名规则:APP_JPUSH_CHANNEL_{VENDOR}_{SYSTEM|CHAT}
|
|
|
|
|
|
viper.BindEnv("jpush.channel.system", "APP_JPUSH_CHANNEL_SYSTEM")
|
|
|
|
|
|
viper.BindEnv("jpush.channel.chat", "APP_JPUSH_CHANNEL_CHAT")
|
|
|
|
|
|
viper.BindEnv("jpush.channel.vendor.xiaomi.system", "APP_JPUSH_CHANNEL_XIAOMI_SYSTEM")
|
|
|
|
|
|
viper.BindEnv("jpush.channel.vendor.xiaomi.chat", "APP_JPUSH_CHANNEL_XIAOMI_CHAT")
|
|
|
|
|
|
viper.BindEnv("jpush.channel.vendor.huawei.system", "APP_JPUSH_CHANNEL_HUAWEI_SYSTEM")
|
|
|
|
|
|
viper.BindEnv("jpush.channel.vendor.huawei.chat", "APP_JPUSH_CHANNEL_HUAWEI_CHAT")
|
|
|
|
|
|
viper.BindEnv("jpush.channel.vendor.oppo.system", "APP_JPUSH_CHANNEL_OPPO_SYSTEM")
|
|
|
|
|
|
viper.BindEnv("jpush.channel.vendor.oppo.chat", "APP_JPUSH_CHANNEL_OPPO_CHAT")
|
|
|
|
|
|
viper.BindEnv("jpush.channel.vendor.vivo.system", "APP_JPUSH_CHANNEL_VIVO_SYSTEM")
|
|
|
|
|
|
viper.BindEnv("jpush.channel.vendor.vivo.chat", "APP_JPUSH_CHANNEL_VIVO_CHAT")
|
|
|
|
|
|
viper.BindEnv("jpush.channel.vendor.meizu.system", "APP_JPUSH_CHANNEL_MEIZU_SYSTEM")
|
|
|
|
|
|
viper.BindEnv("jpush.channel.vendor.meizu.chat", "APP_JPUSH_CHANNEL_MEIZU_CHAT")
|
|
|
|
|
|
viper.BindEnv("jpush.channel.vendor.honor.system", "APP_JPUSH_CHANNEL_HONOR_SYSTEM")
|
|
|
|
|
|
viper.BindEnv("jpush.channel.vendor.honor.chat", "APP_JPUSH_CHANNEL_HONOR_CHAT")
|
|
|
|
|
|
viper.BindEnv("jpush.channel.vendor.fcm.system", "APP_JPUSH_CHANNEL_FCM_SYSTEM")
|
|
|
|
|
|
viper.BindEnv("jpush.channel.vendor.fcm.chat", "APP_JPUSH_CHANNEL_FCM_CHAT")
|
|
|
|
|
|
// 小米/OPPO 模板 id 环境变量绑定
|
|
|
|
|
|
viper.BindEnv("jpush.channel.vendor.xiaomi.mi_system_template_id", "APP_JPUSH_CHANNEL_XIAOMI_MI_SYSTEM_TEMPLATE_ID")
|
|
|
|
|
|
viper.BindEnv("jpush.channel.vendor.xiaomi.mi_chat_template_id", "APP_JPUSH_CHANNEL_XIAOMI_MI_CHAT_TEMPLATE_ID")
|
|
|
|
|
|
viper.BindEnv("jpush.channel.vendor.oppo.oppo_system_private_template_id", "APP_JPUSH_CHANNEL_OPPO_SYSTEM_PRIVATE_TEMPLATE_ID")
|
|
|
|
|
|
viper.BindEnv("jpush.channel.vendor.oppo.oppo_chat_private_template_id", "APP_JPUSH_CHANNEL_OPPO_CHAT_PRIVATE_TEMPLATE_ID")
|
2026-06-18 19:41:32 +08:00
|
|
|
|
// OPPO category / notify_level 环境变量绑定
|
|
|
|
|
|
viper.BindEnv("jpush.channel.vendor.oppo.oppo_system_category", "APP_JPUSH_CHANNEL_OPPO_SYSTEM_CATEGORY")
|
|
|
|
|
|
viper.BindEnv("jpush.channel.vendor.oppo.oppo_chat_category", "APP_JPUSH_CHANNEL_OPPO_CHAT_CATEGORY")
|
|
|
|
|
|
viper.BindEnv("jpush.channel.vendor.oppo.oppo_system_notify_level", "APP_JPUSH_CHANNEL_OPPO_SYSTEM_NOTIFY_LEVEL")
|
|
|
|
|
|
viper.BindEnv("jpush.channel.vendor.oppo.oppo_chat_notify_level", "APP_JPUSH_CHANNEL_OPPO_CHAT_NOTIFY_LEVEL")
|
|
|
|
|
|
// 荣耀 importance 环境变量绑定
|
|
|
|
|
|
viper.BindEnv("jpush.channel.vendor.honor.honor_system_importance", "APP_JPUSH_CHANNEL_HONOR_SYSTEM_IMPORTANCE")
|
|
|
|
|
|
viper.BindEnv("jpush.channel.vendor.honor.honor_chat_importance", "APP_JPUSH_CHANNEL_HONOR_CHAT_IMPORTANCE")
|
|
|
|
|
|
// vivo category 环境变量绑定
|
|
|
|
|
|
viper.BindEnv("jpush.channel.vendor.vivo.vivo_system_category", "APP_JPUSH_CHANNEL_VIVO_SYSTEM_CATEGORY")
|
|
|
|
|
|
viper.BindEnv("jpush.channel.vendor.vivo.vivo_chat_category", "APP_JPUSH_CHANNEL_VIVO_CHAT_CATEGORY")
|
2026-06-17 20:41:55 +08:00
|
|
|
|
// iOS thread-id 环境变量绑定
|
|
|
|
|
|
viper.BindEnv("jpush.channel.ios.chat_thread_id", "APP_JPUSH_CHANNEL_IOS_CHAT_THREAD_ID")
|
|
|
|
|
|
viper.BindEnv("jpush.channel.ios.system_thread_id", "APP_JPUSH_CHANNEL_IOS_SYSTEM_THREAD_ID")
|
2026-04-24 15:14:44 +08:00
|
|
|
|
viper.SetDefault("setup_secret", "")
|
2026-05-06 12:39:11 +08:00
|
|
|
|
// WebSocket 默认值
|
|
|
|
|
|
viper.SetDefault("websocket.mode", "standalone")
|
|
|
|
|
|
viper.SetDefault("websocket.cluster.instance_id", "")
|
|
|
|
|
|
viper.SetDefault("websocket.cluster.msg_channel", "ws:msg")
|
|
|
|
|
|
viper.SetDefault("websocket.cluster.online_ttl", 60)
|
|
|
|
|
|
viper.SetDefault("websocket.cluster.heartbeat_interval", 20)
|
2026-05-07 01:08:39 +08:00
|
|
|
|
// Runner 集群默认值(mode 和 instance_id 空则跟随 websocket)
|
|
|
|
|
|
viper.SetDefault("runner.mode", "")
|
|
|
|
|
|
viper.SetDefault("runner.cluster.instance_id", "")
|
|
|
|
|
|
viper.SetDefault("runner.cluster.task_channel", "runner:task")
|
|
|
|
|
|
viper.SetDefault("runner.cluster.result_channel", "runner:result")
|
|
|
|
|
|
viper.SetDefault("runner.cluster.registry_prefix", "runner:reg:")
|
|
|
|
|
|
viper.SetDefault("runner.cluster.cap_prefix", "runner:cap:")
|
|
|
|
|
|
viper.SetDefault("runner.cluster.registry_ttl", 90)
|
|
|
|
|
|
viper.SetDefault("runner.cluster.heartbeat_interval", 30)
|
2026-05-17 23:38:04 +08:00
|
|
|
|
// SeqBuffer 默认值
|
|
|
|
|
|
viper.SetDefault("seq_buffer.enabled", false)
|
|
|
|
|
|
viper.SetDefault("seq_buffer.private_buffer_size", 50)
|
|
|
|
|
|
viper.SetDefault("seq_buffer.group_buffer_size", 200)
|
|
|
|
|
|
viper.SetDefault("seq_buffer.lock_timeout_ms", 5000)
|
|
|
|
|
|
viper.SetDefault("seq_buffer.max_retries", 3)
|
|
|
|
|
|
// VersionLog 默认值
|
|
|
|
|
|
viper.SetDefault("version_log.enabled", false)
|
|
|
|
|
|
viper.SetDefault("version_log.sync_limit", 100)
|
|
|
|
|
|
viper.SetDefault("version_log.max_sync_gap", 1000)
|
|
|
|
|
|
// PushWorker 默认值
|
|
|
|
|
|
viper.SetDefault("push_worker.enabled", false)
|
|
|
|
|
|
viper.SetDefault("push_worker.stream", "msg_push")
|
|
|
|
|
|
viper.SetDefault("push_worker.group", "push_worker")
|
|
|
|
|
|
viper.SetDefault("push_worker.batch_size", 50)
|
|
|
|
|
|
viper.SetDefault("push_worker.poll_timeout_ms", 2000)
|
|
|
|
|
|
viper.SetDefault("push_worker.max_retries", 3)
|
|
|
|
|
|
viper.SetDefault("push_worker.max_stream_len", 100000)
|
|
|
|
|
|
viper.SetDefault("push_worker.idle_timeout_ms", 30000)
|
2026-06-01 13:41:02 +08:00
|
|
|
|
// LiveKit 默认值
|
|
|
|
|
|
viper.SetDefault("livekit.enabled", false)
|
|
|
|
|
|
viper.SetDefault("livekit.url", "http://localhost:7880")
|
|
|
|
|
|
viper.SetDefault("livekit.api_key", "devkey")
|
|
|
|
|
|
viper.SetDefault("livekit.api_secret", "")
|
|
|
|
|
|
viper.SetDefault("livekit.token_ttl", 600)
|
|
|
|
|
|
viper.SetDefault("livekit.webhook_secret", "")
|
2026-03-09 21:28:58 +08:00
|
|
|
|
|
|
|
|
|
|
if err := viper.ReadInConfig(); err != nil {
|
|
|
|
|
|
return nil, fmt.Errorf("failed to read config: %w", err)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var cfg Config
|
|
|
|
|
|
if err := viper.Unmarshal(&cfg); err != nil {
|
|
|
|
|
|
return nil, fmt.Errorf("failed to unmarshal config: %w", err)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-07 01:08:39 +08:00
|
|
|
|
// Runner mode 和 instance_id 空则跟随 WebSocket 配置
|
|
|
|
|
|
cfg.Runner.ApplyWSDefaults(cfg.WebSocket)
|
|
|
|
|
|
|
2026-03-09 21:28:58 +08:00
|
|
|
|
// Convert seconds to duration
|
|
|
|
|
|
cfg.JWT.AccessTokenExpire = time.Duration(viper.GetInt("jwt.access_token_expire")) * time.Second
|
|
|
|
|
|
cfg.JWT.RefreshTokenExpire = time.Duration(viper.GetInt("jwt.refresh_token_expire")) * time.Second
|
|
|
|
|
|
|
2026-05-04 13:07:03 +08:00
|
|
|
|
// Viper AutomaticEnv 已自动处理所有 APP_ 前缀的环境变量
|
2026-04-24 15:14:44 +08:00
|
|
|
|
|
2026-04-07 00:07:40 +08:00
|
|
|
|
// 安全检查:生产模式必须设置JWT密钥
|
|
|
|
|
|
if cfg.Server.Mode != "debug" {
|
|
|
|
|
|
if cfg.JWT.Secret == "" || cfg.JWT.Secret == "your-jwt-secret-key-change-in-production" {
|
|
|
|
|
|
return nil, fmt.Errorf("SECURITY ERROR: JWT secret must be set via APP_JWT_SECRET environment variable in production mode")
|
|
|
|
|
|
}
|
|
|
|
|
|
if len(cfg.JWT.Secret) < 32 {
|
|
|
|
|
|
return nil, fmt.Errorf("SECURITY ERROR: JWT secret must be at least 32 characters long in production mode")
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-03-09 21:28:58 +08:00
|
|
|
|
|
|
|
|
|
|
return &cfg, nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-04 13:07:03 +08:00
|
|
|
|
|