// Package config defines cron-service runtime configuration. package config import ( "strings" "hyapp/pkg/appcode" "hyapp/pkg/configx" "hyapp/pkg/logx" ) const defaultAppCode = "lalu" // Config describes cron-service startup and schedule settings. type Config struct { // ServiceName is written into logs and health metadata. ServiceName string `yaml:"service_name"` // NodeID identifies this scheduler instance in run history and task leases. NodeID string `yaml:"node_id"` // Environment is the runtime environment tag used by structured logs. Environment string `yaml:"environment"` // GRPCAddr exposes gRPC health and future internal management APIs. GRPCAddr string `yaml:"grpc_addr"` // HealthHTTPAddr exposes HTTP health for CLB and deploy scripts only. HealthHTTPAddr string `yaml:"health_http_addr"` // MySQLDSN stores cron run history and schedule-level leases only. MySQLDSN string `yaml:"mysql_dsn"` // UserServiceAddr is the owner endpoint for user/auth/host batch tasks. UserServiceAddr string `yaml:"user_service_addr"` // ActivityServiceAddr is the owner endpoint for activity/message batch tasks. ActivityServiceAddr string `yaml:"activity_service_addr"` // GameServiceAddr is the owner endpoint for game/order relay batch tasks. GameServiceAddr string `yaml:"game_service_addr"` // WalletServiceAddr is the owner endpoint for wallet/VIP/settlement tasks. WalletServiceAddr string `yaml:"wallet_service_addr"` // Tasks contains per-task schedule knobs. Disabled tasks are documented but not started. Tasks map[string]TaskConfig `yaml:"tasks"` // Log controls stdout structured logging. Log logx.Config `yaml:"log"` } // TaskConfig keeps scheduler knobs as strings so YAML stays easy to read. type TaskConfig struct { // Enabled controls whether cron-service starts a loop for this task. Enabled bool `yaml:"enabled"` // Interval is the delay between successful schedule attempts. Interval string `yaml:"interval"` // Timeout bounds the owner service RPC call for one batch. Timeout string `yaml:"timeout"` // LockTTL is the owner service task claim lock budget. LockTTL string `yaml:"lock_ttl"` // BatchSize is passed to owner service batch RPCs. BatchSize int `yaml:"batch_size"` // AppCodes scopes the task by tenant; every app_code gets an independent run. AppCodes []string `yaml:"app_codes"` // PendingPublishMaxAge is used by mic_open_session_compensation for pending_publish sessions. PendingPublishMaxAge string `yaml:"pending_publish_max_age"` // PublishingSessionMaxAge is used by mic_open_session_compensation for publishing sessions. PublishingSessionMaxAge string `yaml:"publishing_session_max_age"` } // Default returns local development defaults that follow the 13xxx port rule. func Default() Config { return Config{ ServiceName: "cron-service", NodeID: "cron-local", Environment: "local", GRPCAddr: ":13007", HealthHTTPAddr: ":13107", MySQLDSN: "hyapp:hyapp@tcp(127.0.0.1:23306)/hyapp_cron?parseTime=true&charset=utf8mb4&loc=UTC", UserServiceAddr: "127.0.0.1:13005", ActivityServiceAddr: "127.0.0.1:13006", GameServiceAddr: "127.0.0.1:13008", WalletServiceAddr: "127.0.0.1:13004", Tasks: defaultTasks(), Log: logx.Config{ Level: "info", Format: "json", MaxPayloadBytes: 2048, }, } } // Load reads a YAML file into Config and normalizes empty fields. func Load(path string) (Config, error) { cfg := Default() if path == "" { return cfg, nil } if err := configx.LoadYAML(path, &cfg); err != nil { return Config{}, err } normalize(&cfg) return cfg, nil } func normalize(cfg *Config) { cfg.ServiceName = strings.TrimSpace(cfg.ServiceName) if cfg.ServiceName == "" { cfg.ServiceName = "cron-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.GRPCAddr = strings.TrimSpace(cfg.GRPCAddr) if cfg.GRPCAddr == "" { cfg.GRPCAddr = ":13007" } cfg.HealthHTTPAddr = strings.TrimSpace(cfg.HealthHTTPAddr) if cfg.HealthHTTPAddr == "" { cfg.HealthHTTPAddr = ":13107" } cfg.GameServiceAddr = strings.TrimSpace(cfg.GameServiceAddr) if cfg.GameServiceAddr == "" { cfg.GameServiceAddr = "127.0.0.1:13008" } cfg.Tasks = normalizeTasks(cfg.Tasks) } func defaultTasks() map[string]TaskConfig { return map[string]TaskConfig{ "login_ip_risk": { Enabled: true, Interval: "5s", Timeout: "3s", LockTTL: "30s", BatchSize: 100, AppCodes: []string{defaultAppCode}, }, "user_region_rebuild": { Enabled: true, Interval: "5s", Timeout: "5s", LockTTL: "30s", BatchSize: 500, AppCodes: []string{defaultAppCode}, }, "message_fanout": { Enabled: true, Interval: "1s", Timeout: "10s", LockTTL: "30s", BatchSize: 500, AppCodes: []string{defaultAppCode}, }, "growth_level_reward": { Enabled: true, Interval: "5s", Timeout: "10s", LockTTL: "30s", BatchSize: 100, AppCodes: []string{defaultAppCode}, }, "achievement_reward": { Enabled: true, Interval: "5s", Timeout: "10s", LockTTL: "30s", BatchSize: 100, AppCodes: []string{defaultAppCode}, }, "game_level_event_relay": { Enabled: true, Interval: "5s", Timeout: "10s", LockTTL: "30s", BatchSize: 100, AppCodes: []string{defaultAppCode}, }, "host_salary_daily_settlement": { Enabled: true, Interval: "24h", Timeout: "30s", LockTTL: "2m", BatchSize: 200, AppCodes: []string{defaultAppCode}, }, "host_salary_half_month_settlement": { Enabled: true, Interval: "24h", Timeout: "30s", LockTTL: "2m", BatchSize: 200, AppCodes: []string{defaultAppCode}, }, "host_salary_month_end": { Enabled: true, Interval: "24h", Timeout: "60s", LockTTL: "5m", BatchSize: 200, AppCodes: []string{defaultAppCode}, }, "mic_open_session_compensation": { Enabled: true, Interval: "60s", Timeout: "10s", LockTTL: "60s", BatchSize: 100, AppCodes: []string{defaultAppCode}, PendingPublishMaxAge: "2m", PublishingSessionMaxAge: "12h", }, } } func normalizeTasks(tasks map[string]TaskConfig) map[string]TaskConfig { defaults := defaultTasks() if tasks == nil { tasks = map[string]TaskConfig{} } for name, def := range defaults { current, ok := tasks[name] if !ok { tasks[name] = def continue } tasks[name] = mergeTaskConfig(def, current) } for name, task := range tasks { tasks[name] = mergeTaskConfig(TaskConfig{ Interval: "5s", Timeout: "5s", LockTTL: "30s", BatchSize: 100, AppCodes: []string{defaultAppCode}, }, task) } return tasks } func mergeTaskConfig(def TaskConfig, current TaskConfig) TaskConfig { current.Interval = strings.TrimSpace(current.Interval) if current.Interval == "" { current.Interval = def.Interval } current.Timeout = strings.TrimSpace(current.Timeout) if current.Timeout == "" { current.Timeout = def.Timeout } current.LockTTL = strings.TrimSpace(current.LockTTL) if current.LockTTL == "" { current.LockTTL = def.LockTTL } current.PendingPublishMaxAge = strings.TrimSpace(current.PendingPublishMaxAge) if current.PendingPublishMaxAge == "" { current.PendingPublishMaxAge = def.PendingPublishMaxAge } current.PublishingSessionMaxAge = strings.TrimSpace(current.PublishingSessionMaxAge) if current.PublishingSessionMaxAge == "" { current.PublishingSessionMaxAge = def.PublishingSessionMaxAge } if current.BatchSize <= 0 { current.BatchSize = def.BatchSize } current.AppCodes = normalizeAppCodes(current.AppCodes) if len(current.AppCodes) == 0 { current.AppCodes = def.AppCodes } return current } func normalizeAppCodes(values []string) []string { seen := map[string]bool{} result := make([]string, 0, len(values)) for _, value := range values { code := appcode.Normalize(value) if code == "" || seen[code] { continue } seen[code] = true result = append(result, code) } return result }