package config import ( "errors" "fmt" "net" "os" "strconv" "time" ) type Config struct { HTTPAddr string MySQLDSN string MaxOpen int MaxIdle int MetricsAddr string WorkflowTickInterval time.Duration // WorkflowTickInterval Weave 持久化推进扫描间隔,范围100ms–1m。 ScheduleTickInterval time.Duration // ScheduleTickInterval 计划扫描间隔,范围100ms–1m。 RecoveryTickInterval time.Duration // RecoveryTickInterval 执行租约恢复扫描间隔,范围100ms–1m。 RuntimeBatchLimit int // RuntimeBatchLimit 每个后台批次的候选上限,范围1–100。 ShutdownTimeout time.Duration // ShutdownTimeout 优雅排空和关闭窗口,范围1s–5m。 } func Load() (Config, error) { return FromEnv(os.Getenv) } func FromEnv(get func(string) string) (Config, error) { c := Config{ HTTPAddr: get("ORBIT_HTTP_ADDR"), MySQLDSN: get("ORBIT_MYSQL_DSN"), MaxOpen: 20, MaxIdle: 5, MetricsAddr: get("ORBIT_METRICS_ADDR"), WorkflowTickInterval: time.Second, ScheduleTickInterval: time.Second, RecoveryTickInterval: 2 * time.Second, RuntimeBatchLimit: 100, ShutdownTimeout: 10 * time.Second, } if c.HTTPAddr == "" { c.HTTPAddr = "127.0.0.1:8080" } if _, _, err := net.SplitHostPort(c.HTTPAddr); err != nil { return Config{}, errors.New("invalid ORBIT_HTTP_ADDR") } if c.MySQLDSN == "" { return Config{}, errors.New("ORBIT_MYSQL_DSN is required") } for _, field := range []struct { name string dst *int }{{"ORBIT_DB_MAX_OPEN", &c.MaxOpen}, {"ORBIT_DB_MAX_IDLE", &c.MaxIdle}} { if raw := get(field.name); raw != "" { n, err := strconv.Atoi(raw) if err != nil || n < 0 { return Config{}, fmt.Errorf("%s must be a nonnegative integer", field.name) } *field.dst = n } } if c.MaxOpen <= 0 || c.MaxIdle > c.MaxOpen { return Config{}, errors.New("database pool requires 0 <= max_idle <= max_open and max_open > 0") } for _, field := range []struct { name string dst *time.Duration }{{"ORBIT_WORKFLOW_TICK_INTERVAL", &c.WorkflowTickInterval}, {"ORBIT_SCHEDULE_TICK_INTERVAL", &c.ScheduleTickInterval}, {"ORBIT_RECOVERY_TICK_INTERVAL", &c.RecoveryTickInterval}} { if raw := get(field.name); raw != "" { d, err := time.ParseDuration(raw) if err != nil || d < 100*time.Millisecond || d > time.Minute { return Config{}, fmt.Errorf("%s must be a duration from 100ms to 1m", field.name) } *field.dst = d } } if raw := get("ORBIT_RUNTIME_BATCH_LIMIT"); raw != "" { n, err := strconv.Atoi(raw) if err != nil || n < 1 || n > 100 { return Config{}, errors.New("ORBIT_RUNTIME_BATCH_LIMIT must be an integer from 1 to 100") } c.RuntimeBatchLimit = n } if raw := get("ORBIT_SHUTDOWN_TIMEOUT"); raw != "" { d, err := time.ParseDuration(raw) if err != nil || d < time.Second || d > 5*time.Minute { return Config{}, errors.New("ORBIT_SHUTDOWN_TIMEOUT must be a duration from 1s to 5m") } c.ShutdownTimeout = d } if c.MetricsAddr != "" { _, port, err := net.SplitHostPort(c.MetricsAddr) n, parseErr := strconv.Atoi(port) if err != nil || parseErr != nil || n < 0 || n > 65535 { return Config{}, errors.New("invalid ORBIT_METRICS_ADDR") } } return c, nil }