2026-05-12 19:34:47 +08:00

290 lines
12 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package config
import (
"fmt"
"strings"
"time"
"hyapp/pkg/configx"
"hyapp/pkg/logx"
"hyapp/pkg/tencentrtc"
)
// Config 描述 gateway-service 启动所需的最小配置。
type Config struct {
ServiceName string `yaml:"service_name"`
NodeID string `yaml:"node_id"`
Environment string `yaml:"environment"`
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"`
ActivityServiceAddr string `yaml:"activity_service_addr"`
GameServiceAddr string `yaml:"game_service_addr"`
AuthRateLimit AuthRateLimitConfig `yaml:"auth_rate_limit"`
LoginRisk LoginRiskConfig `yaml:"login_risk"`
AppConfig AppConfigConfig `yaml:"app_config"`
TencentIM TencentIMConfig `yaml:"tencent_im"`
TencentRTC TencentRTCConfig `yaml:"tencent_rtc"`
TencentCOS TencentCOSConfig `yaml:"tencent-cos"`
Log logx.Config `yaml:"log"`
}
// LoginRiskConfig 控制 gateway 登录入口快拦和 Redis 风控缓存读取。
type LoginRiskConfig struct {
Enabled bool `yaml:"enabled"`
RedisAddr string `yaml:"redis_addr"`
RedisPassword string `yaml:"redis_password"`
RedisDB int `yaml:"redis_db"`
KeyPrefix string `yaml:"key_prefix"`
FastGuard LoginRiskFastGuardConfig `yaml:"fast_guard"`
}
// LoginRiskFastGuardConfig 保存语言和时区弱信号阻断配置。
type LoginRiskFastGuardConfig struct {
BlockedLanguages []string `yaml:"blocked_languages"`
BlockedLanguagePrimaryTags []string `yaml:"blocked_language_primary_tags"`
BlockedTimezones []string `yaml:"blocked_timezones"`
BlockedTimezonePrefixes []string `yaml:"blocked_timezone_prefixes"`
}
// AppConfigConfig 描述 gateway 读取后台 App 运行时配置的只读数据源。
type AppConfigConfig struct {
// MySQLDSN 指向后台管理库 hyapp_admingateway 只读 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"`
// AdminIdentifier 是服务端 REST 发播报和系统消息的管理员账号,回调用它识别服务端发送者。
AdminIdentifier string `yaml:"admin_identifier"`
// 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{
ServiceName: "gateway-service",
NodeID: "gateway-local",
Environment: "local",
HTTPAddr: ":13000",
JWTSecret: "gateway-dev-secret",
RoomServiceAddr: "127.0.0.1:13001",
UserServiceAddr: "127.0.0.1:13005",
WalletServiceAddr: "127.0.0.1:13004",
ActivityServiceAddr: "127.0.0.1:13006",
GameServiceAddr: "127.0.0.1:13008",
AppConfig: AppConfigConfig{
MySQLDSN: "hyapp:hyapp@tcp(127.0.0.1:23306)/hyapp_admin?parseTime=true&charset=utf8mb4&loc=UTC",
},
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,
},
LoginRisk: LoginRiskConfig{
Enabled: true,
RedisAddr: "127.0.0.1:13379",
KeyPrefix: "login_risk:ip:",
},
TencentIM: TencentIMConfig{
UserSigTTL: 24 * time.Hour,
AdminIdentifier: "administrator",
},
TencentRTC: TencentRTCConfig{
UserSigTTL: 2 * time.Hour,
RoomIDType: "string",
AppScene: "voice_chat_room",
},
Log: logx.Config{
Level: "info",
Format: "json",
MaxPayloadBytes: 2048,
},
}
}
// 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.ServiceName = strings.TrimSpace(cfg.ServiceName)
if cfg.ServiceName == "" {
cfg.ServiceName = "gateway-service"
}
cfg.NodeID = strings.TrimSpace(cfg.NodeID)
if cfg.NodeID == "" {
cfg.NodeID = cfg.ServiceName
}
cfg.Environment = strings.ToLower(strings.TrimSpace(cfg.Environment))
if cfg.Environment == "" {
cfg.Environment = "local"
}
cfg.RoomServiceAddr = strings.TrimSpace(cfg.RoomServiceAddr)
cfg.UserServiceAddr = strings.TrimSpace(cfg.UserServiceAddr)
cfg.WalletServiceAddr = strings.TrimSpace(cfg.WalletServiceAddr)
cfg.ActivityServiceAddr = strings.TrimSpace(cfg.ActivityServiceAddr)
cfg.GameServiceAddr = strings.TrimSpace(cfg.GameServiceAddr)
if cfg.WalletServiceAddr == "" {
cfg.WalletServiceAddr = "127.0.0.1:13004"
}
if cfg.ActivityServiceAddr == "" {
cfg.ActivityServiceAddr = "127.0.0.1:13006"
}
if cfg.GameServiceAddr == "" {
cfg.GameServiceAddr = "127.0.0.1:13008"
}
cfg.AppConfig.MySQLDSN = strings.TrimSpace(cfg.AppConfig.MySQLDSN)
cfg.LoginRisk.RedisAddr = strings.TrimSpace(cfg.LoginRisk.RedisAddr)
cfg.LoginRisk.KeyPrefix = strings.TrimSpace(cfg.LoginRisk.KeyPrefix)
if cfg.LoginRisk.Enabled && cfg.LoginRisk.RedisAddr == "" {
cfg.LoginRisk.RedisAddr = strings.TrimSpace(cfg.AuthRateLimit.RedisAddr)
cfg.LoginRisk.RedisPassword = cfg.AuthRateLimit.RedisPassword
cfg.LoginRisk.RedisDB = cfg.AuthRateLimit.RedisDB
}
if cfg.LoginRisk.KeyPrefix == "" {
cfg.LoginRisk.KeyPrefix = "login_risk:ip:"
}
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)
cfg.TencentIM.AdminIdentifier = strings.TrimSpace(cfg.TencentIM.AdminIdentifier)
if cfg.TencentIM.AdminIdentifier == "" {
cfg.TencentIM.AdminIdentifier = "administrator"
}
cfg.TencentIM.CallbackURL = strings.TrimSpace(cfg.TencentIM.CallbackURL)
cfg.TencentIM.CallbackAuthToken = strings.TrimSpace(cfg.TencentIM.CallbackAuthToken)
return nil
}