2026-05-12 21:51:39 +08:00

226 lines
6.8 KiB
Go

// 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"`
// 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",
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.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},
},
"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
}