2026-05-25 15:45:57 +08:00

213 lines
6.8 KiB
Go

package config
import (
"errors"
"fmt"
"strings"
"time"
"hyapp/pkg/configx"
"hyapp/pkg/logx"
)
// 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"`
LaunchSessionTTL time.Duration `yaml:"launch_session_ttl"`
RocketMQ RocketMQConfig `yaml:"rocketmq"`
OutboxWorker OutboxWorkerConfig `yaml:"outbox_worker"`
Log logx.Config `yaml:"log"`
}
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",
LaunchSessionTTL: 15 * time.Minute,
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")
}
if cfg.LaunchSessionTTL <= 0 {
cfg.LaunchSessionTTL = 15 * time.Minute
}
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 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
}