package config import ( "fmt" "strings" "time" "hyapp/pkg/configx" "hyapp/pkg/tencentrtc" ) // Config 描述 gateway-service 启动所需的最小配置。 type Config struct { HTTPAddr string `yaml:"http_addr"` JWTSecret string `yaml:"jwt_secret"` RoomServiceAddr string `yaml:"room_service_addr"` UserServiceAddr string `yaml:"user_service_addr"` WalletServiceAddr string `yaml:"wallet_service_addr"` AuthRateLimit AuthRateLimitConfig `yaml:"auth_rate_limit"` AppConfig AppConfigConfig `yaml:"app_config"` TencentIM TencentIMConfig `yaml:"tencent_im"` TencentRTC TencentRTCConfig `yaml:"tencent_rtc"` TencentCOS TencentCOSConfig `yaml:"tencent-cos"` } // AppConfigConfig 描述 gateway 读取后台 App 运行时配置的只读数据源。 type AppConfigConfig struct { // MySQLDSN 指向后台管理库 hyapp_admin;gateway 只读 admin_app_configs。 MySQLDSN string `yaml:"mysql_dsn"` } // AuthRateLimitConfig 控制 gateway public auth 入口的 Redis 频控。 type AuthRateLimitConfig struct { // Enabled 为 false 时关闭 gateway auth 入口频控。 Enabled bool `yaml:"enabled"` // RedisAddr 是 public auth 频控 Redis 地址。 RedisAddr string `yaml:"redis_addr"` // RedisPassword 是 Redis 鉴权密码,本地默认留空。 RedisPassword string `yaml:"redis_password"` // RedisDB 是 Redis logical DB。 RedisDB int `yaml:"redis_db"` // KeyPrefix 是频控 key 前缀,用于区分环境和服务。 KeyPrefix string `yaml:"key_prefix"` // WindowSec 是固定窗口秒数。 WindowSec int64 `yaml:"window_sec"` // IPLimit 限制同一入口 IP 对所有 public auth API 的请求数。 IPLimit int `yaml:"ip_limit"` // DeviceIDLimit 限制同一 device_id 对登录、注册和 refresh 的请求数。 DeviceIDLimit int `yaml:"device_id_limit"` // ProviderIPLimit 限制同一 IP 对同一三方 provider 的注册/登录请求数。 ProviderIPLimit int `yaml:"provider_ip_limit"` // DisplayUserIDIPLimit 限制同一 IP 对同一 display_user_id 的密码尝试。 DisplayUserIDIPLimit int `yaml:"display_user_id_ip_limit"` // DisplayUserIDLimit 限制同一 display_user_id 被跨 IP 喷射尝试。 DisplayUserIDLimit int `yaml:"display_user_id_limit"` // SessionIDIPLimit 限制同一 IP 对同一 session_id 的 logout 请求数。 SessionIDIPLimit int `yaml:"session_id_ip_limit"` } // TencentIMConfig 描述 gateway 给客户端签发腾讯云 IM UserSig 的配置。 type TencentIMConfig struct { // SDKAppID 是腾讯云 IM 控制台分配的应用 ID。 SDKAppID int64 `yaml:"sdk_app_id"` // SecretKey 只允许出现在服务端配置中,不能下发给客户端。 SecretKey string `yaml:"secret_key"` // UserSigTTL 是客户端 IM 登录票据有效期。 UserSigTTL time.Duration `yaml:"user_sig_ttl"` // CallbackURL 是在腾讯云 IM 控制台配置的服务端回调地址。 CallbackURL string `yaml:"callback_url"` // CallbackAuthToken 是腾讯云 IM 回调鉴权 token,只用于校验回调来源。 CallbackAuthToken string `yaml:"callback_auth_token"` } // TencentRTCConfig 描述 gateway 给客户端签发腾讯 RTC 进房 UserSig 的配置。 type TencentRTCConfig struct { // Enabled 控制 RTC token endpoint 是否可以对外签发,配置缺失时必须 fail-closed。 Enabled bool `yaml:"enabled"` // SDKAppID 是腾讯 RTC 控制台分配的应用 ID。 SDKAppID int64 `yaml:"sdk_app_id"` // SecretKey 只允许出现在服务端配置中,不能下发给客户端。 SecretKey string `yaml:"secret_key"` // UserSigTTL 是客户端 RTC 进房票据有效期。 UserSigTTL time.Duration `yaml:"user_sig_ttl"` // RoomIDType 首版固定 string,确保客户端只使用 TRTC strRoomId。 RoomIDType string `yaml:"room_id_type"` // AppScene 首版固定 voice_chat_room,所有端必须一致。 AppScene string `yaml:"app_scene"` // CallbackURL 是在腾讯 RTC 控制台配置的事件回调地址。 CallbackURL string `yaml:"callback_url"` // CallbackSignKey 是腾讯 RTC 事件回调签名 key,只用于校验回调来源。 CallbackSignKey string `yaml:"callback_sign_key"` } // TencentCOSConfig 描述 gateway 代 App 上传文件到腾讯云 COS 所需的服务端配置。 type TencentCOSConfig struct { // Enabled 控制上传接口是否真正写 COS;关闭时接口会 fail-closed。 Enabled bool `yaml:"enabled"` // SecretID/SecretKey 只允许保存在服务端配置中,不能下发给客户端。 SecretID string `yaml:"secret-id"` SecretKey string `yaml:"secret-key"` // BucketName 是完整 bucket 名,例如 yumi-assets-1420526837。 BucketName string `yaml:"bucket-name"` // Region 是 COS 地域,例如 me-saudi-arabia。 Region string `yaml:"region"` // AccessURL 是对象对外访问域名,通常是 CDN 或绑定的自定义域名。 AccessURL string `yaml:"access-url"` } // Default 返回本地开发默认配置。 func Default() Config { return Config{ HTTPAddr: ":13000", JWTSecret: "gateway-dev-secret", RoomServiceAddr: "127.0.0.1:13001", UserServiceAddr: "127.0.0.1:13005", WalletServiceAddr: "127.0.0.1:13004", AppConfig: AppConfigConfig{ MySQLDSN: "hyapp:hyapp@tcp(127.0.0.1:23306)/hyapp_admin?parseTime=true&charset=utf8mb4&loc=Local", }, AuthRateLimit: AuthRateLimitConfig{ Enabled: true, RedisAddr: "127.0.0.1:13379", KeyPrefix: "gateway:auth:rate:", WindowSec: 60, IPLimit: 120, DeviceIDLimit: 30, ProviderIPLimit: 20, DisplayUserIDIPLimit: 10, DisplayUserIDLimit: 50, SessionIDIPLimit: 30, }, TencentIM: TencentIMConfig{ UserSigTTL: 24 * time.Hour, }, TencentRTC: TencentRTCConfig{ UserSigTTL: 2 * time.Hour, RoomIDType: "string", AppScene: "voice_chat_room", }, } } // Load 从独立 config.yaml 读取 gateway-service 配置。 func Load(path string) (Config, error) { cfg := Default() if path == "" { if err := cfg.Normalize(); err != nil { return Config{}, err } return cfg, nil } if err := configx.LoadYAML(path, &cfg); err != nil { return Config{}, err } if err := cfg.Normalize(); err != nil { return Config{}, err } return cfg, nil } // Normalize fills policy defaults and rejects unsupported RTC room/app modes at startup. func (cfg *Config) Normalize() error { cfg.RoomServiceAddr = strings.TrimSpace(cfg.RoomServiceAddr) cfg.UserServiceAddr = strings.TrimSpace(cfg.UserServiceAddr) cfg.WalletServiceAddr = strings.TrimSpace(cfg.WalletServiceAddr) if cfg.WalletServiceAddr == "" { cfg.WalletServiceAddr = "127.0.0.1:13004" } cfg.AppConfig.MySQLDSN = strings.TrimSpace(cfg.AppConfig.MySQLDSN) cfg.TencentCOS.SecretID = strings.TrimSpace(cfg.TencentCOS.SecretID) cfg.TencentCOS.SecretKey = strings.TrimSpace(cfg.TencentCOS.SecretKey) cfg.TencentCOS.BucketName = strings.TrimSpace(cfg.TencentCOS.BucketName) cfg.TencentCOS.Region = strings.TrimSpace(cfg.TencentCOS.Region) cfg.TencentCOS.AccessURL = strings.TrimRight(strings.TrimSpace(cfg.TencentCOS.AccessURL), "/") if cfg.TencentCOS.Enabled { if cfg.TencentCOS.SecretID == "" || cfg.TencentCOS.SecretKey == "" || cfg.TencentCOS.BucketName == "" || cfg.TencentCOS.Region == "" || cfg.TencentCOS.AccessURL == "" { return fmt.Errorf("tencent-cos config is incomplete") } if !strings.HasPrefix(cfg.TencentCOS.AccessURL, "http://") && !strings.HasPrefix(cfg.TencentCOS.AccessURL, "https://") { return fmt.Errorf("tencent-cos.access-url must be an absolute url") } } cfg.TencentRTC.RoomIDType = strings.TrimSpace(cfg.TencentRTC.RoomIDType) if cfg.TencentRTC.RoomIDType == "" { cfg.TencentRTC.RoomIDType = tencentrtc.RoomIDTypeString } if cfg.TencentRTC.RoomIDType != tencentrtc.RoomIDTypeString { // Numeric TRTC roomId would fork users into a different room than internal string room_id. return fmt.Errorf("tencent_rtc.room_id_type must be %q", tencentrtc.RoomIDTypeString) } cfg.TencentRTC.AppScene = strings.TrimSpace(cfg.TencentRTC.AppScene) if cfg.TencentRTC.AppScene == "" { cfg.TencentRTC.AppScene = tencentrtc.AppSceneVoiceChatRoom } if cfg.TencentRTC.AppScene != tencentrtc.AppSceneVoiceChatRoom { // v1 has no gateway policy surface for multiple RTC scenes, so reject drift early. return fmt.Errorf("tencent_rtc.app_scene must be %q", tencentrtc.AppSceneVoiceChatRoom) } cfg.TencentRTC.CallbackURL = strings.TrimSpace(cfg.TencentRTC.CallbackURL) cfg.TencentRTC.CallbackSignKey = strings.TrimSpace(cfg.TencentRTC.CallbackSignKey) return nil }