411 lines
14 KiB
Go
411 lines
14 KiB
Go
package config
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"os"
|
|
"strings"
|
|
"time"
|
|
|
|
"hyapp-admin-server/internal/platform/logging"
|
|
|
|
"gopkg.in/yaml.v3"
|
|
)
|
|
|
|
type Config struct {
|
|
ServiceName string `yaml:"service_name"`
|
|
NodeID string `yaml:"node_id"`
|
|
Environment string `yaml:"environment"`
|
|
Log logging.Config `yaml:"log"`
|
|
HTTPAddr string `yaml:"http_addr"`
|
|
MySQLDSN string `yaml:"mysql_dsn"`
|
|
UserMySQLDSN string `yaml:"user_mysql_dsn"`
|
|
WalletMySQLDSN string `yaml:"wallet_mysql_dsn"`
|
|
RoomMySQLDSN string `yaml:"room_mysql_dsn"`
|
|
MySQLAutoMigrate bool `yaml:"mysql_auto_migrate"`
|
|
Migrations MigrationConfig `yaml:"migrations"`
|
|
JWTSecret string `yaml:"jwt_secret"`
|
|
AccessTokenTTL time.Duration `yaml:"access_token_ttl"`
|
|
RefreshTokenTTL time.Duration `yaml:"refresh_token_ttl"`
|
|
CORSAllowedOrigins []string `yaml:"cors_allowed_origins"`
|
|
RefreshCookieSecure bool `yaml:"refresh_cookie_secure"`
|
|
Bootstrap BootstrapConfig `yaml:"bootstrap"`
|
|
BootstrapPassword string `yaml:"bootstrap_password"`
|
|
UserService UserServiceConfig `yaml:"user_service"`
|
|
TencentCOS TencentCOSConfig `yaml:"tencent-cos"`
|
|
Redis RedisConfig `yaml:"redis"`
|
|
Jobs JobsConfig `yaml:"jobs"`
|
|
WalletService WalletServiceConfig `yaml:"wallet_service"`
|
|
RoomService RoomServiceConfig `yaml:"room_service"`
|
|
ActivityService ActivityServiceConfig `yaml:"activity_service"`
|
|
GameService GameServiceConfig `yaml:"game_service"`
|
|
}
|
|
|
|
type MigrationConfig struct {
|
|
Enabled bool `yaml:"enabled"`
|
|
Dir string `yaml:"dir"`
|
|
AllowChecksumRepair bool `yaml:"allow_checksum_repair"`
|
|
}
|
|
|
|
type UserServiceConfig struct {
|
|
Addr string `yaml:"addr"`
|
|
RequestTimeout time.Duration `yaml:"request_timeout"`
|
|
}
|
|
|
|
type WalletServiceConfig struct {
|
|
Addr string `yaml:"addr"`
|
|
RequestTimeout time.Duration `yaml:"request_timeout"`
|
|
}
|
|
|
|
type RoomServiceConfig struct {
|
|
Addr string `yaml:"addr"`
|
|
RequestTimeout time.Duration `yaml:"request_timeout"`
|
|
}
|
|
|
|
type ActivityServiceConfig struct {
|
|
Addr string `yaml:"addr"`
|
|
RequestTimeout time.Duration `yaml:"request_timeout"`
|
|
}
|
|
|
|
type GameServiceConfig struct {
|
|
Addr string `yaml:"addr"`
|
|
RequestTimeout time.Duration `yaml:"request_timeout"`
|
|
}
|
|
|
|
type TencentCOSConfig struct {
|
|
Enabled bool `yaml:"enabled"`
|
|
SecretID string `yaml:"secret-id"`
|
|
SecretKey string `yaml:"secret-key"`
|
|
BucketName string `yaml:"bucket-name"`
|
|
Region string `yaml:"region"`
|
|
AccessURL string `yaml:"access-url"`
|
|
ObjectPrefix string `yaml:"object-prefix"`
|
|
}
|
|
|
|
type BootstrapConfig struct {
|
|
Enabled bool `yaml:"enabled"`
|
|
SeedDemoData bool `yaml:"seed_demo_data"`
|
|
Username string `yaml:"username"`
|
|
Name string `yaml:"name"`
|
|
Team string `yaml:"team"`
|
|
Password string `yaml:"password"`
|
|
}
|
|
|
|
type RedisConfig struct {
|
|
Enabled bool `yaml:"enabled"`
|
|
Addr string `yaml:"addr"`
|
|
Password string `yaml:"password"`
|
|
DB int `yaml:"db"`
|
|
KeyPrefix string `yaml:"key_prefix"`
|
|
}
|
|
|
|
type JobsConfig struct {
|
|
Enabled bool `yaml:"enabled"`
|
|
WorkerInterval time.Duration `yaml:"worker_interval"`
|
|
LeaseTTL time.Duration `yaml:"lease_ttl"`
|
|
MaxAttempts int `yaml:"max_attempts"`
|
|
ArtifactDir string `yaml:"artifact_dir"`
|
|
}
|
|
|
|
func Default() Config {
|
|
return Config{
|
|
ServiceName: "admin-server",
|
|
NodeID: "admin-local",
|
|
Environment: "local",
|
|
Log: logging.DefaultConfig(),
|
|
HTTPAddr: ":13100",
|
|
MySQLDSN: "hyapp:hyapp@tcp(127.0.0.1:23306)/hyapp_admin?parseTime=true&charset=utf8mb4&loc=UTC",
|
|
UserMySQLDSN: "hyapp:hyapp@tcp(127.0.0.1:23306)/hyapp_user?parseTime=true&charset=utf8mb4&loc=UTC",
|
|
WalletMySQLDSN: "hyapp:hyapp@tcp(127.0.0.1:23306)/hyapp_wallet?parseTime=true&charset=utf8mb4&loc=UTC",
|
|
RoomMySQLDSN: "hyapp:hyapp@tcp(127.0.0.1:23306)/hyapp_room?parseTime=true&charset=utf8mb4&loc=UTC",
|
|
MySQLAutoMigrate: true,
|
|
Migrations: MigrationConfig{
|
|
Enabled: true,
|
|
Dir: "migrations",
|
|
AllowChecksumRepair: true,
|
|
},
|
|
JWTSecret: "dev-shared-secret",
|
|
AccessTokenTTL: 30 * time.Minute,
|
|
RefreshTokenTTL: 7 * 24 * time.Hour,
|
|
CORSAllowedOrigins: []string{
|
|
"http://localhost:7001",
|
|
"http://127.0.0.1:7001",
|
|
},
|
|
RefreshCookieSecure: false,
|
|
Bootstrap: BootstrapConfig{
|
|
Enabled: true,
|
|
SeedDemoData: true,
|
|
Username: "admin",
|
|
Name: "admin",
|
|
Team: "平台运维",
|
|
Password: "admin123",
|
|
},
|
|
BootstrapPassword: "admin123",
|
|
UserService: UserServiceConfig{
|
|
Addr: "127.0.0.1:13005",
|
|
RequestTimeout: 3 * time.Second,
|
|
},
|
|
WalletService: WalletServiceConfig{
|
|
Addr: "127.0.0.1:13004",
|
|
RequestTimeout: 3 * time.Second,
|
|
},
|
|
RoomService: RoomServiceConfig{
|
|
Addr: "127.0.0.1:13001",
|
|
RequestTimeout: 3 * time.Second,
|
|
},
|
|
ActivityService: ActivityServiceConfig{
|
|
Addr: "127.0.0.1:13006",
|
|
RequestTimeout: 3 * time.Second,
|
|
},
|
|
GameService: GameServiceConfig{
|
|
Addr: "127.0.0.1:13008",
|
|
RequestTimeout: 3 * time.Second,
|
|
},
|
|
TencentCOS: TencentCOSConfig{
|
|
Enabled: false,
|
|
ObjectPrefix: "admin",
|
|
},
|
|
Redis: RedisConfig{
|
|
Enabled: true,
|
|
Addr: "127.0.0.1:13379",
|
|
KeyPrefix: "admin:",
|
|
},
|
|
Jobs: JobsConfig{
|
|
Enabled: true,
|
|
WorkerInterval: 5 * time.Second,
|
|
LeaseTTL: 2 * time.Minute,
|
|
MaxAttempts: 3,
|
|
ArtifactDir: "storage/exports",
|
|
},
|
|
}
|
|
}
|
|
|
|
func Load(path string) (Config, error) {
|
|
cfg := Default()
|
|
if path == "" {
|
|
return cfg, nil
|
|
}
|
|
|
|
body, err := os.ReadFile(path)
|
|
if err != nil {
|
|
return Config{}, err
|
|
}
|
|
|
|
if err := yaml.Unmarshal(body, &cfg); err != nil {
|
|
return Config{}, err
|
|
}
|
|
|
|
cfg.Normalize()
|
|
if err := cfg.Validate(); err != nil {
|
|
return Config{}, err
|
|
}
|
|
|
|
return cfg, nil
|
|
}
|
|
|
|
func (cfg *Config) Normalize() {
|
|
cfg.Environment = strings.ToLower(strings.TrimSpace(cfg.Environment))
|
|
if cfg.Environment == "" {
|
|
cfg.Environment = "local"
|
|
}
|
|
if cfg.Log.Level == "" {
|
|
cfg.Log.Level = "info"
|
|
}
|
|
if cfg.Log.Format == "" {
|
|
cfg.Log.Format = "json"
|
|
}
|
|
if cfg.Log.MaxPayloadBytes <= 0 {
|
|
cfg.Log.MaxPayloadBytes = 2048
|
|
}
|
|
cfg.ServiceName = strings.TrimSpace(cfg.ServiceName)
|
|
cfg.NodeID = strings.TrimSpace(cfg.NodeID)
|
|
if cfg.NodeID == "" {
|
|
cfg.NodeID = cfg.ServiceName
|
|
}
|
|
if cfg.Bootstrap.Password == "" && cfg.BootstrapPassword != "" {
|
|
cfg.Bootstrap.Password = cfg.BootstrapPassword
|
|
}
|
|
if cfg.Bootstrap.Username == "" {
|
|
cfg.Bootstrap.Username = "admin"
|
|
}
|
|
if cfg.Bootstrap.Name == "" {
|
|
cfg.Bootstrap.Name = cfg.Bootstrap.Username
|
|
}
|
|
if cfg.Bootstrap.Team == "" {
|
|
cfg.Bootstrap.Team = "平台运维"
|
|
}
|
|
if cfg.Jobs.LeaseTTL <= 0 {
|
|
cfg.Jobs.LeaseTTL = 2 * time.Minute
|
|
}
|
|
if cfg.Jobs.MaxAttempts <= 0 {
|
|
cfg.Jobs.MaxAttempts = 3
|
|
}
|
|
if cfg.Jobs.ArtifactDir == "" {
|
|
cfg.Jobs.ArtifactDir = "storage/exports"
|
|
}
|
|
cfg.UserService.Addr = strings.TrimSpace(cfg.UserService.Addr)
|
|
if cfg.UserService.RequestTimeout <= 0 {
|
|
cfg.UserService.RequestTimeout = 3 * time.Second
|
|
}
|
|
cfg.WalletService.Addr = strings.TrimSpace(cfg.WalletService.Addr)
|
|
if cfg.WalletService.RequestTimeout <= 0 {
|
|
cfg.WalletService.RequestTimeout = 3 * time.Second
|
|
}
|
|
cfg.RoomService.Addr = strings.TrimSpace(cfg.RoomService.Addr)
|
|
if cfg.RoomService.Addr == "" {
|
|
cfg.RoomService.Addr = "127.0.0.1:13001"
|
|
}
|
|
if cfg.RoomService.RequestTimeout <= 0 {
|
|
cfg.RoomService.RequestTimeout = 3 * time.Second
|
|
}
|
|
cfg.ActivityService.Addr = strings.TrimSpace(cfg.ActivityService.Addr)
|
|
if cfg.ActivityService.RequestTimeout <= 0 {
|
|
cfg.ActivityService.RequestTimeout = 3 * time.Second
|
|
}
|
|
cfg.GameService.Addr = strings.TrimSpace(cfg.GameService.Addr)
|
|
if cfg.GameService.Addr == "" {
|
|
cfg.GameService.Addr = "127.0.0.1:13008"
|
|
}
|
|
if cfg.GameService.RequestTimeout <= 0 {
|
|
cfg.GameService.RequestTimeout = 3 * time.Second
|
|
}
|
|
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), "/")
|
|
cfg.TencentCOS.ObjectPrefix = strings.Trim(strings.TrimSpace(cfg.TencentCOS.ObjectPrefix), "/")
|
|
if cfg.TencentCOS.ObjectPrefix == "" {
|
|
cfg.TencentCOS.ObjectPrefix = "admin"
|
|
}
|
|
}
|
|
|
|
func (cfg Config) Validate() error {
|
|
if !validEnvironment(cfg.Environment) {
|
|
return errors.New("environment must be one of local, dev, staging, prod")
|
|
}
|
|
if strings.TrimSpace(cfg.ServiceName) == "" {
|
|
return errors.New("service_name is required")
|
|
}
|
|
if strings.TrimSpace(cfg.NodeID) == "" {
|
|
return errors.New("node_id is required")
|
|
}
|
|
if strings.TrimSpace(cfg.HTTPAddr) == "" {
|
|
return errors.New("http_addr is required")
|
|
}
|
|
if strings.TrimSpace(cfg.MySQLDSN) == "" {
|
|
return errors.New("mysql_dsn is required")
|
|
}
|
|
if strings.TrimSpace(cfg.UserMySQLDSN) == "" {
|
|
return errors.New("user_mysql_dsn is required")
|
|
}
|
|
if strings.TrimSpace(cfg.WalletMySQLDSN) == "" {
|
|
return errors.New("wallet_mysql_dsn is required")
|
|
}
|
|
if strings.TrimSpace(cfg.RoomMySQLDSN) == "" {
|
|
return errors.New("room_mysql_dsn is required")
|
|
}
|
|
if cfg.Migrations.Enabled && strings.TrimSpace(cfg.Migrations.Dir) == "" {
|
|
return errors.New("migrations.dir is required when migrations are enabled")
|
|
}
|
|
if isLockedEnvironment(cfg.Environment) && cfg.Migrations.AllowChecksumRepair {
|
|
return errors.New("migrations.allow_checksum_repair must be false outside local/dev")
|
|
}
|
|
if strings.TrimSpace(cfg.JWTSecret) == "" {
|
|
return errors.New("jwt_secret is required")
|
|
}
|
|
if cfg.AccessTokenTTL <= 0 {
|
|
return errors.New("access_token_ttl must be greater than 0")
|
|
}
|
|
if cfg.RefreshTokenTTL <= 0 {
|
|
return errors.New("refresh_token_ttl must be greater than 0")
|
|
}
|
|
if isLockedEnvironment(cfg.Environment) {
|
|
if cfg.JWTSecret == "dev-shared-secret" || len(cfg.JWTSecret) < 32 {
|
|
return errors.New("jwt_secret must be a non-default value with at least 32 characters outside local/dev")
|
|
}
|
|
if cfg.MySQLAutoMigrate {
|
|
return errors.New("mysql_auto_migrate must be false outside local/dev")
|
|
}
|
|
if cfg.Bootstrap.Enabled && cfg.Bootstrap.Password == "admin123" {
|
|
return errors.New("bootstrap.password must not use the default value outside local/dev")
|
|
}
|
|
}
|
|
if len(cfg.CORSAllowedOrigins) == 0 {
|
|
return errors.New("cors_allowed_origins must not be empty")
|
|
}
|
|
if cfg.Bootstrap.Enabled && strings.TrimSpace(cfg.Bootstrap.Password) == "" {
|
|
return errors.New("bootstrap.password is required when bootstrap is enabled")
|
|
}
|
|
if strings.TrimSpace(cfg.UserService.Addr) == "" {
|
|
return errors.New("user_service.addr is required")
|
|
}
|
|
if cfg.UserService.RequestTimeout <= 0 {
|
|
return errors.New("user_service.request_timeout must be greater than 0")
|
|
}
|
|
if strings.TrimSpace(cfg.WalletService.Addr) == "" {
|
|
return errors.New("wallet_service.addr is required")
|
|
}
|
|
if cfg.WalletService.RequestTimeout <= 0 {
|
|
return errors.New("wallet_service.request_timeout must be greater than 0")
|
|
}
|
|
if strings.TrimSpace(cfg.RoomService.Addr) == "" {
|
|
return errors.New("room_service.addr is required")
|
|
}
|
|
if cfg.RoomService.RequestTimeout <= 0 {
|
|
return errors.New("room_service.request_timeout must be greater than 0")
|
|
}
|
|
if strings.TrimSpace(cfg.ActivityService.Addr) == "" {
|
|
return errors.New("activity_service.addr is required")
|
|
}
|
|
if cfg.ActivityService.RequestTimeout <= 0 {
|
|
return errors.New("activity_service.request_timeout must be greater than 0")
|
|
}
|
|
if strings.TrimSpace(cfg.GameService.Addr) == "" {
|
|
return errors.New("game_service.addr is required")
|
|
}
|
|
if cfg.GameService.RequestTimeout <= 0 {
|
|
return errors.New("game_service.request_timeout must be greater than 0")
|
|
}
|
|
if cfg.TencentCOS.Enabled {
|
|
if cfg.TencentCOS.SecretID == "" || cfg.TencentCOS.SecretKey == "" || cfg.TencentCOS.BucketName == "" || cfg.TencentCOS.Region == "" || cfg.TencentCOS.AccessURL == "" {
|
|
return errors.New("tencent-cos config is incomplete")
|
|
}
|
|
if !strings.HasPrefix(cfg.TencentCOS.AccessURL, "http://") && !strings.HasPrefix(cfg.TencentCOS.AccessURL, "https://") {
|
|
return errors.New("tencent-cos.access-url must be an absolute url")
|
|
}
|
|
}
|
|
if cfg.Redis.Enabled && strings.TrimSpace(cfg.Redis.Addr) == "" {
|
|
return errors.New("redis.addr is required when redis is enabled")
|
|
}
|
|
if cfg.Redis.DB < 0 {
|
|
return fmt.Errorf("redis.db must be greater than or equal to 0")
|
|
}
|
|
if cfg.Jobs.Enabled && cfg.Jobs.WorkerInterval <= 0 {
|
|
return errors.New("jobs.worker_interval must be greater than 0")
|
|
}
|
|
if cfg.Jobs.Enabled && cfg.Jobs.LeaseTTL <= 0 {
|
|
return errors.New("jobs.lease_ttl must be greater than 0")
|
|
}
|
|
if cfg.Jobs.Enabled && cfg.Jobs.MaxAttempts <= 0 {
|
|
return errors.New("jobs.max_attempts must be greater than 0")
|
|
}
|
|
if cfg.Jobs.Enabled && strings.TrimSpace(cfg.Jobs.ArtifactDir) == "" {
|
|
return errors.New("jobs.artifact_dir is required when jobs are enabled")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func validEnvironment(value string) bool {
|
|
switch value {
|
|
case "local", "dev", "staging", "prod":
|
|
return true
|
|
default:
|
|
return false
|
|
}
|
|
}
|
|
|
|
func isLockedEnvironment(value string) bool {
|
|
return value == "staging" || value == "prod"
|
|
}
|