2026-06-11 19:24:51 +08:00

513 lines
21 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"
"net/http"
"strings"
"time"
"hyapp/pkg/configx"
"hyapp/pkg/grpcclient"
"hyapp/pkg/logx"
"hyapp/pkg/tencentim"
"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"`
CORS CORSConfig `yaml:"cors"`
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"`
Statistics StatisticsConfig `yaml:"statistics"`
GRPCClient GRPCClientConfig `yaml:"grpc_client"`
AuthRateLimit AuthRateLimitConfig `yaml:"auth_rate_limit"`
LoginRisk LoginRiskConfig `yaml:"login_risk"`
AppConfig AppConfigConfig `yaml:"app_config"`
Leaderboard LeaderboardConfig `yaml:"leaderboard"`
TencentIM TencentIMConfig `yaml:"tencent_im"`
TencentRTC TencentRTCConfig `yaml:"tencent_rtc"`
TencentCOS TencentCOSConfig `yaml:"tencent-cos"`
Log logx.Config `yaml:"log"`
}
// CORSConfig 控制浏览器/H5 访问 gateway API 的跨域入口策略。
type CORSConfig struct {
// Enabled 为 true 时gateway 在最外层处理浏览器 preflight避免 OPTIONS 进入业务 handler。
Enabled bool `yaml:"enabled"`
// AllowedOrigins 是允许携带凭据访问 API 的 Origin 白名单;生产不要配置裸星号。
AllowedOrigins []string `yaml:"allowed_origins"`
// AllowedMethods 是 preflight 允许的方法集合。
AllowedMethods []string `yaml:"allowed_methods"`
// AllowedHeaders 是 preflight 允许的请求头集合。
AllowedHeaders []string `yaml:"allowed_headers"`
// ExposeHeaders 是实际响应暴露给浏览器读取的响应头集合。
ExposeHeaders []string `yaml:"expose_headers"`
// AllowCredentials 控制浏览器是否允许带 cookie/Authorization 等凭据。
AllowCredentials bool `yaml:"allow_credentials"`
// MaxAgeSec 是浏览器缓存 preflight 结果的秒数。
MaxAgeSec int64 `yaml:"max_age_sec"`
}
// GRPCClientConfig 控制 gateway 访问内部服务的 gRPC transport 策略。
type GRPCClientConfig struct {
// DefaultTimeout 是没有显式 request deadline 时的 RPC 总预算,包含重试耗时。
DefaultTimeout time.Duration `yaml:"default_timeout"`
// ConnectTimeout 是单次建连最小预算,避免内网目标不可达时长期卡住。
ConnectTimeout time.Duration `yaml:"connect_timeout"`
// KeepaliveTime 是空闲连接探活周期,帮助 CLB/NAT 后的半开连接尽早发现。
KeepaliveTime time.Duration `yaml:"keepalive_time"`
// KeepaliveTimeout 是 keepalive ack 等待时间。
KeepaliveTimeout time.Duration `yaml:"keepalive_timeout"`
// RetryMaxAttempts 是包含首次调用在内的最大尝试次数;小于 2 时关闭显式重试。
RetryMaxAttempts int `yaml:"retry_max_attempts"`
// RetryInitialBackoff 是第一次重试前等待时间。
RetryInitialBackoff time.Duration `yaml:"retry_initial_backoff"`
// RetryMaxBackoff 是重试退避上限。
RetryMaxBackoff time.Duration `yaml:"retry_max_backoff"`
// RetryBackoffMultiplier 控制退避增长倍率。
RetryBackoffMultiplier float64 `yaml:"retry_backoff_multiplier"`
// RetryableStatusCodes 限定可重试状态,生产默认只允许 UNAVAILABLE。
RetryableStatusCodes []string `yaml:"retryable_status_codes"`
}
// Runtime 转换为共享 gRPC client 配置,避免 app 层重复维护默认值。
func (c GRPCClientConfig) Runtime() grpcclient.Config {
return grpcclient.Normalize(grpcclient.Config{
DefaultTimeout: c.DefaultTimeout,
ConnectTimeout: c.ConnectTimeout,
KeepaliveTime: c.KeepaliveTime,
KeepaliveTimeout: c.KeepaliveTimeout,
RetryMaxAttempts: c.RetryMaxAttempts,
RetryInitialBackoff: c.RetryInitialBackoff,
RetryMaxBackoff: c.RetryMaxBackoff,
RetryBackoffMultiplier: c.RetryBackoffMultiplier,
RetryableStatusCodes: c.RetryableStatusCodes,
})
}
// 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 只读 App 运行时配置表。
MySQLDSN string `yaml:"mysql_dsn"`
}
// LeaderboardConfig 描述 App 活动榜单读取钱包送礼事实的只读数据源。
type LeaderboardConfig struct {
// WalletMySQLDSN 指向钱包库 hyapp_wallet只读取 wallet_transactions 的 gift_debit 事实。
WalletMySQLDSN string `yaml:"wallet_mysql_dsn"`
}
// StatisticsConfig 描述 gateway 转发客户端 H5 埋点到 statistics-service 的内部 HTTP 入口。
type StatisticsConfig struct {
BaseURL string `yaml:"base_url"`
Timeout time.Duration `yaml:"timeout"`
}
// 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"`
// AdminUserSigTTL 控制 gateway 补偿导入 IM 账号时的 admin UserSig 有效期。
AdminUserSigTTL time.Duration `yaml:"admin_user_sig_ttl"`
// Endpoint 是腾讯 IM REST 地域入口,例如 adminapisgp.im.qcloud.com。
Endpoint string `yaml:"endpoint"`
// RequestTimeout 是 gateway 调腾讯 IM REST 的请求超时。
RequestTimeout time.Duration `yaml:"request_timeout"`
// GroupIDPrefix 给测试服等隔离环境使用,正式服为空以保持既有腾讯 IM 群不变。
GroupIDPrefix string `yaml:"group_id_prefix"`
// CallbackURL 是在腾讯云 IM 控制台配置的服务端回调地址。
CallbackURL string `yaml:"callback_url"`
// CallbackAuthToken 是腾讯云 IM 回调鉴权 token只用于校验回调来源。
CallbackAuthToken string `yaml:"callback_auth_token"`
}
// RESTConfig 转换成腾讯 IM REST client 配置,用于 /im/usersig 入口补偿导入账号。
func (c TencentIMConfig) RESTConfig() tencentim.RESTConfig {
timeout := c.RequestTimeout
if timeout <= 0 {
timeout = 5 * time.Second
}
ttl := c.AdminUserSigTTL
if ttl <= 0 {
ttl = 24 * time.Hour
}
return tencentim.RESTConfig{
SDKAppID: c.SDKAppID,
SecretKey: c.SecretKey,
AdminIdentifier: c.AdminIdentifier,
AdminUserSigTTL: ttl,
Endpoint: c.Endpoint,
GroupType: tencentim.DefaultGroupType,
HTTPClient: &http.Client{Timeout: timeout},
}
}
// 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",
CORS: CORSConfig{
Enabled: true,
AllowedOrigins: []string{
"http://localhost:3000",
"http://127.0.0.1:3000",
"http://localhost:5173",
"http://127.0.0.1:5173",
"https://global-interaction.com",
"https://www.global-interaction.com",
"https://h5.global-interaction.com",
"https://api.global-interaction.com",
},
AllowedMethods: []string{"GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"},
AllowedHeaders: []string{
"Authorization",
"Content-Type",
"X-Request-ID",
"X-App-Code",
"X-HY-App-Code",
"X-App-Package",
"X-Package-Name",
"X-App-Bundle-ID",
"X-Bundle-ID",
"X-App-Platform",
"X-Platform",
},
ExposeHeaders: []string{"X-Request-ID"},
AllowCredentials: true,
MaxAgeSec: 600,
},
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",
Statistics: StatisticsConfig{
BaseURL: "http://127.0.0.1:13010",
Timeout: 2 * time.Second,
},
GRPCClient: GRPCClientConfig{
DefaultTimeout: 5 * time.Second,
ConnectTimeout: 2 * time.Second,
KeepaliveTime: 30 * time.Second,
KeepaliveTimeout: 10 * time.Second,
RetryMaxAttempts: 2,
RetryInitialBackoff: 100 * time.Millisecond,
RetryMaxBackoff: 500 * time.Millisecond,
RetryBackoffMultiplier: 2,
RetryableStatusCodes: []string{"UNAVAILABLE"},
},
AppConfig: AppConfigConfig{
MySQLDSN: "hyapp:hyapp@tcp(127.0.0.1:23306)/hyapp_admin?parseTime=true&charset=utf8mb4&loc=UTC",
},
Leaderboard: LeaderboardConfig{
WalletMySQLDSN: "hyapp:hyapp@tcp(127.0.0.1:23306)/hyapp_wallet?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,
AdminUserSigTTL: 24 * time.Hour,
AdminIdentifier: "administrator",
RequestTimeout: 5 * time.Second,
Endpoint: tencentim.DefaultEndpoint,
},
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.CORS.AllowedOrigins = trimNonEmpty(cfg.CORS.AllowedOrigins)
cfg.CORS.AllowedMethods = upperNonEmpty(cfg.CORS.AllowedMethods)
cfg.CORS.AllowedHeaders = trimNonEmpty(cfg.CORS.AllowedHeaders)
cfg.CORS.ExposeHeaders = trimNonEmpty(cfg.CORS.ExposeHeaders)
if len(cfg.CORS.AllowedMethods) == 0 {
cfg.CORS.AllowedMethods = []string{"GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"}
}
if len(cfg.CORS.AllowedHeaders) == 0 {
cfg.CORS.AllowedHeaders = []string{"Authorization", "Content-Type", "X-Request-ID", "X-App-Code", "X-HY-App-Code", "X-App-Package", "X-Package-Name", "X-App-Bundle-ID", "X-Bundle-ID", "X-App-Platform", "X-Platform"}
}
if len(cfg.CORS.ExposeHeaders) == 0 {
cfg.CORS.ExposeHeaders = []string{"X-Request-ID"}
}
if cfg.CORS.MaxAgeSec <= 0 {
cfg.CORS.MaxAgeSec = 600
}
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)
cfg.Statistics.BaseURL = strings.TrimRight(strings.TrimSpace(cfg.Statistics.BaseURL), "/")
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"
}
if cfg.Statistics.BaseURL == "" {
cfg.Statistics.BaseURL = "http://127.0.0.1:13010"
}
if cfg.Statistics.Timeout <= 0 {
cfg.Statistics.Timeout = 2 * time.Second
}
runtimeGRPC := cfg.GRPCClient.Runtime()
cfg.GRPCClient = GRPCClientConfig{
DefaultTimeout: runtimeGRPC.DefaultTimeout,
ConnectTimeout: runtimeGRPC.ConnectTimeout,
KeepaliveTime: runtimeGRPC.KeepaliveTime,
KeepaliveTimeout: runtimeGRPC.KeepaliveTimeout,
RetryMaxAttempts: runtimeGRPC.RetryMaxAttempts,
RetryInitialBackoff: runtimeGRPC.RetryInitialBackoff,
RetryMaxBackoff: runtimeGRPC.RetryMaxBackoff,
RetryBackoffMultiplier: runtimeGRPC.RetryBackoffMultiplier,
RetryableStatusCodes: runtimeGRPC.RetryableStatusCodes,
}
cfg.AppConfig.MySQLDSN = strings.TrimSpace(cfg.AppConfig.MySQLDSN)
cfg.Leaderboard.WalletMySQLDSN = strings.TrimSpace(cfg.Leaderboard.WalletMySQLDSN)
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.Endpoint = strings.TrimSpace(cfg.TencentIM.Endpoint)
if cfg.TencentIM.Endpoint == "" {
cfg.TencentIM.Endpoint = tencentim.DefaultEndpoint
}
if cfg.TencentIM.AdminUserSigTTL <= 0 {
cfg.TencentIM.AdminUserSigTTL = 24 * time.Hour
}
if cfg.TencentIM.RequestTimeout <= 0 {
cfg.TencentIM.RequestTimeout = 5 * time.Second
}
cfg.TencentIM.GroupIDPrefix = strings.TrimSpace(cfg.TencentIM.GroupIDPrefix)
cfg.TencentIM.CallbackURL = strings.TrimSpace(cfg.TencentIM.CallbackURL)
cfg.TencentIM.CallbackAuthToken = strings.TrimSpace(cfg.TencentIM.CallbackAuthToken)
return nil
}
func trimNonEmpty(values []string) []string {
out := make([]string, 0, len(values))
for _, value := range values {
value = strings.TrimSpace(value)
if value != "" {
out = append(out, value)
}
}
return out
}
func upperNonEmpty(values []string) []string {
out := make([]string, 0, len(values))
for _, value := range values {
value = strings.ToUpper(strings.TrimSpace(value))
if value != "" {
out = append(out, value)
}
}
return out
}