297 lines
10 KiB
Go
297 lines
10 KiB
Go
package config
|
||
|
||
import (
|
||
"errors"
|
||
"fmt"
|
||
"net/http"
|
||
"strings"
|
||
"time"
|
||
|
||
"hyapp/pkg/configx"
|
||
"hyapp/pkg/logx"
|
||
"hyapp/pkg/tencentim"
|
||
)
|
||
|
||
// Config 描述 game-service 启动所需的最小依赖。
|
||
type Config struct {
|
||
ServiceName string `yaml:"service_name"`
|
||
NodeID string `yaml:"node_id"`
|
||
Environment string `yaml:"environment"`
|
||
GRPCAddr string `yaml:"grpc_addr"`
|
||
// HealthHTTPAddr 是给 CLB/发布脚本探活使用的 HTTP 监听地址,不承载业务流量。
|
||
HealthHTTPAddr string `yaml:"health_http_addr"`
|
||
MySQLDSN string `yaml:"mysql_dsn"`
|
||
WalletServiceAddr string `yaml:"wallet_service_addr"`
|
||
UserServiceAddr string `yaml:"user_service_addr"`
|
||
ActivityServiceAddr string `yaml:"activity_service_addr"`
|
||
RobotServiceAddr string `yaml:"robot_service_addr"`
|
||
LaunchSessionTTL time.Duration `yaml:"launch_session_ttl"`
|
||
// TencentIM 是房内猜拳向房间群投递 PK 事件的腾讯 IM REST 配置;缺配置时必须显式关闭,不能靠环境变量隐式漂移。
|
||
TencentIM TencentIMConfig `yaml:"tencent_im"`
|
||
RocketMQ RocketMQConfig `yaml:"rocketmq"`
|
||
OutboxWorker OutboxWorkerConfig `yaml:"outbox_worker"`
|
||
Log logx.Config `yaml:"log"`
|
||
}
|
||
|
||
// TencentIMConfig 描述 game-service 发房内 RPS 群自定义消息需要的服务端 IM 配置。
|
||
type TencentIMConfig struct {
|
||
// Enabled 控制房内 RPS 是否向腾讯 IM 房间群投递事件;开启后缺少密钥或管理员账号应启动失败。
|
||
Enabled bool `yaml:"enabled"`
|
||
// SDKAppID 是腾讯云 IM 应用 ID,必须和 gateway 签发 UserSig 使用同一个应用。
|
||
SDKAppID int64 `yaml:"sdk_app_id"`
|
||
// SecretKey 只用于服务端签 admin UserSig,不能出现在响应或业务日志里。
|
||
SecretKey string `yaml:"secret_key"`
|
||
// AdminIdentifier 是服务端 REST 发房间 PK 消息的管理员账号。
|
||
AdminIdentifier string `yaml:"admin_identifier"`
|
||
// AdminUserSigTTL 控制 REST 调用签名有效期,默认 24h。
|
||
AdminUserSigTTL time.Duration `yaml:"admin_user_sig_ttl"`
|
||
// Endpoint 是腾讯 IM REST 地域入口,例如 adminapisgp.im.qcloud.com。
|
||
Endpoint string `yaml:"endpoint"`
|
||
// GroupType 是房间群类型;房内 RPS 复用房间群,默认 ChatRoom。
|
||
GroupType string `yaml:"group_type"`
|
||
// RequestTimeout 是单次腾讯 IM REST 调用预算,避免创建 PK 时无限等待外部服务。
|
||
RequestTimeout time.Duration `yaml:"request_timeout"`
|
||
}
|
||
|
||
// RESTConfig 收敛为共享腾讯 IM client 配置,调用方不用重复处理默认超时和群类型。
|
||
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: c.GroupType,
|
||
HTTPClient: &http.Client{Timeout: timeout},
|
||
}
|
||
}
|
||
|
||
type RocketMQConfig struct {
|
||
Enabled bool `yaml:"enabled"`
|
||
NameServers []string `yaml:"name_servers"`
|
||
NameServerDomain string `yaml:"name_server_domain"`
|
||
AccessKey string `yaml:"access_key"`
|
||
SecretKey string `yaml:"secret_key"`
|
||
SecurityToken string `yaml:"security_token"`
|
||
Namespace string `yaml:"namespace"`
|
||
VIPChannel bool `yaml:"vip_channel"`
|
||
SendTimeout time.Duration `yaml:"send_timeout"`
|
||
Retry int `yaml:"retry"`
|
||
GameOutbox GameOutboxConfig `yaml:"game_outbox"`
|
||
}
|
||
|
||
type GameOutboxConfig struct {
|
||
Enabled bool `yaml:"enabled"`
|
||
Topic string `yaml:"topic"`
|
||
ProducerGroup string `yaml:"producer_group"`
|
||
}
|
||
|
||
type OutboxWorkerConfig struct {
|
||
Enabled bool `yaml:"enabled"`
|
||
PollInterval time.Duration `yaml:"poll_interval"`
|
||
BatchSize int `yaml:"batch_size"`
|
||
PublishTimeout time.Duration `yaml:"publish_timeout"`
|
||
}
|
||
|
||
// Default 返回本地开发默认配置。
|
||
func Default() Config {
|
||
return Config{
|
||
ServiceName: "game-service",
|
||
NodeID: "game-local",
|
||
Environment: "local",
|
||
GRPCAddr: ":13008",
|
||
HealthHTTPAddr: ":13108",
|
||
MySQLDSN: "hyapp:hyapp@tcp(127.0.0.1:23306)/hyapp_game?parseTime=true&charset=utf8mb4&loc=UTC&multiStatements=true",
|
||
WalletServiceAddr: "127.0.0.1:13004",
|
||
UserServiceAddr: "127.0.0.1:13005",
|
||
ActivityServiceAddr: "127.0.0.1:13006",
|
||
RobotServiceAddr: "127.0.0.1:13011",
|
||
LaunchSessionTTL: 15 * time.Minute,
|
||
TencentIM: TencentIMConfig{
|
||
AdminIdentifier: "administrator",
|
||
AdminUserSigTTL: 24 * time.Hour,
|
||
Endpoint: tencentim.DefaultEndpoint,
|
||
GroupType: tencentim.DefaultGroupType,
|
||
RequestTimeout: 5 * time.Second,
|
||
},
|
||
RocketMQ: defaultRocketMQConfig(),
|
||
OutboxWorker: OutboxWorkerConfig{
|
||
Enabled: false,
|
||
PollInterval: time.Second,
|
||
BatchSize: 100,
|
||
PublishTimeout: 3 * time.Second,
|
||
},
|
||
Log: logx.Config{
|
||
Level: "info",
|
||
Format: "json",
|
||
MaxPayloadBytes: 2048,
|
||
},
|
||
}
|
||
}
|
||
|
||
// Load 从 YAML 文件读取配置,并填充默认值。
|
||
func Load(path string) (Config, error) {
|
||
cfg := Default()
|
||
if path != "" {
|
||
if err := configx.LoadYAML(path, &cfg); err != nil {
|
||
return Config{}, err
|
||
}
|
||
}
|
||
if err := cfg.Normalize(); err != nil {
|
||
return Config{}, err
|
||
}
|
||
return cfg, nil
|
||
}
|
||
|
||
// Normalize 在启动时收敛配置,避免运行期才暴露关键依赖缺失。
|
||
func (cfg *Config) Normalize() error {
|
||
cfg.ServiceName = strings.TrimSpace(cfg.ServiceName)
|
||
if cfg.ServiceName == "" {
|
||
cfg.ServiceName = "game-service"
|
||
}
|
||
cfg.NodeID = strings.TrimSpace(cfg.NodeID)
|
||
if cfg.NodeID == "" {
|
||
cfg.NodeID = cfg.ServiceName
|
||
}
|
||
cfg.Environment = strings.TrimSpace(cfg.Environment)
|
||
if cfg.Environment == "" {
|
||
cfg.Environment = "local"
|
||
}
|
||
cfg.GRPCAddr = strings.TrimSpace(cfg.GRPCAddr)
|
||
if cfg.GRPCAddr == "" {
|
||
cfg.GRPCAddr = ":13008"
|
||
}
|
||
cfg.HealthHTTPAddr = strings.TrimSpace(cfg.HealthHTTPAddr)
|
||
if cfg.HealthHTTPAddr == "" {
|
||
cfg.HealthHTTPAddr = ":13108"
|
||
}
|
||
cfg.MySQLDSN = strings.TrimSpace(cfg.MySQLDSN)
|
||
if cfg.MySQLDSN == "" {
|
||
return fmt.Errorf("mysql_dsn is required")
|
||
}
|
||
cfg.WalletServiceAddr = strings.TrimSpace(cfg.WalletServiceAddr)
|
||
if cfg.WalletServiceAddr == "" {
|
||
return fmt.Errorf("wallet_service_addr is required")
|
||
}
|
||
cfg.UserServiceAddr = strings.TrimSpace(cfg.UserServiceAddr)
|
||
if cfg.UserServiceAddr == "" {
|
||
return fmt.Errorf("user_service_addr is required")
|
||
}
|
||
cfg.ActivityServiceAddr = strings.TrimSpace(cfg.ActivityServiceAddr)
|
||
if cfg.ActivityServiceAddr == "" {
|
||
return fmt.Errorf("activity_service_addr is required")
|
||
}
|
||
cfg.RobotServiceAddr = strings.TrimSpace(cfg.RobotServiceAddr)
|
||
if cfg.RobotServiceAddr == "" {
|
||
return fmt.Errorf("robot_service_addr is required")
|
||
}
|
||
if cfg.LaunchSessionTTL <= 0 {
|
||
cfg.LaunchSessionTTL = 15 * time.Minute
|
||
}
|
||
cfg.normalizeTencentIM()
|
||
if cfg.TencentIM.Enabled && (cfg.TencentIM.SDKAppID <= 0 || cfg.TencentIM.SecretKey == "" || cfg.TencentIM.AdminIdentifier == "") {
|
||
return errors.New("tencent_im enabled requires sdk_app_id, secret_key and admin_identifier")
|
||
}
|
||
cfg.RocketMQ = normalizeRocketMQConfig(cfg.RocketMQ)
|
||
if cfg.OutboxWorker.PollInterval <= 0 {
|
||
cfg.OutboxWorker.PollInterval = time.Second
|
||
}
|
||
if cfg.OutboxWorker.BatchSize <= 0 {
|
||
cfg.OutboxWorker.BatchSize = 100
|
||
}
|
||
if cfg.OutboxWorker.PublishTimeout <= 0 {
|
||
cfg.OutboxWorker.PublishTimeout = 3 * time.Second
|
||
}
|
||
if cfg.OutboxWorker.Enabled && !cfg.RocketMQ.GameOutbox.Enabled {
|
||
return errors.New("outbox_worker requires rocketmq.game_outbox.enabled")
|
||
}
|
||
if cfg.RocketMQ.Enabled {
|
||
if len(cfg.RocketMQ.NameServers) == 0 && cfg.RocketMQ.NameServerDomain == "" {
|
||
return errors.New("rocketmq name_servers or name_server_domain is required")
|
||
}
|
||
if (cfg.RocketMQ.AccessKey == "") != (cfg.RocketMQ.SecretKey == "") {
|
||
return errors.New("rocketmq access_key and secret_key must be configured together")
|
||
}
|
||
}
|
||
return nil
|
||
}
|
||
|
||
func (cfg *Config) normalizeTencentIM() {
|
||
cfg.TencentIM.SecretKey = strings.TrimSpace(cfg.TencentIM.SecretKey)
|
||
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
|
||
}
|
||
cfg.TencentIM.GroupType = strings.TrimSpace(cfg.TencentIM.GroupType)
|
||
if cfg.TencentIM.GroupType == "" {
|
||
cfg.TencentIM.GroupType = tencentim.DefaultGroupType
|
||
}
|
||
if cfg.TencentIM.AdminUserSigTTL <= 0 {
|
||
cfg.TencentIM.AdminUserSigTTL = 24 * time.Hour
|
||
}
|
||
if cfg.TencentIM.RequestTimeout <= 0 {
|
||
cfg.TencentIM.RequestTimeout = 5 * time.Second
|
||
}
|
||
}
|
||
|
||
func defaultRocketMQConfig() RocketMQConfig {
|
||
return RocketMQConfig{
|
||
Enabled: false,
|
||
SendTimeout: 3 * time.Second,
|
||
Retry: 2,
|
||
GameOutbox: GameOutboxConfig{
|
||
Enabled: false,
|
||
Topic: "hyapp_game_outbox",
|
||
ProducerGroup: "hyapp-game-outbox-producer",
|
||
},
|
||
}
|
||
}
|
||
|
||
func normalizeRocketMQConfig(cfg RocketMQConfig) RocketMQConfig {
|
||
defaults := defaultRocketMQConfig()
|
||
cfg.NameServers = normalizeStringSlice(cfg.NameServers)
|
||
cfg.NameServerDomain = strings.TrimSpace(cfg.NameServerDomain)
|
||
cfg.AccessKey = strings.TrimSpace(cfg.AccessKey)
|
||
cfg.SecretKey = strings.TrimSpace(cfg.SecretKey)
|
||
cfg.SecurityToken = strings.TrimSpace(cfg.SecurityToken)
|
||
cfg.Namespace = strings.TrimSpace(cfg.Namespace)
|
||
if cfg.SendTimeout <= 0 {
|
||
cfg.SendTimeout = defaults.SendTimeout
|
||
}
|
||
if cfg.Retry <= 0 {
|
||
cfg.Retry = defaults.Retry
|
||
}
|
||
if cfg.GameOutbox.Topic = strings.TrimSpace(cfg.GameOutbox.Topic); cfg.GameOutbox.Topic == "" {
|
||
cfg.GameOutbox.Topic = defaults.GameOutbox.Topic
|
||
}
|
||
if cfg.GameOutbox.ProducerGroup = strings.TrimSpace(cfg.GameOutbox.ProducerGroup); cfg.GameOutbox.ProducerGroup == "" {
|
||
cfg.GameOutbox.ProducerGroup = defaults.GameOutbox.ProducerGroup
|
||
}
|
||
if cfg.GameOutbox.Enabled {
|
||
cfg.Enabled = true
|
||
}
|
||
return cfg
|
||
}
|
||
|
||
func normalizeStringSlice(values []string) []string {
|
||
normalized := make([]string, 0, len(values))
|
||
for _, value := range values {
|
||
if value = strings.TrimSpace(value); value != "" {
|
||
normalized = append(normalized, value)
|
||
}
|
||
}
|
||
return normalized
|
||
}
|