package config // 本文件承载「由**组合根**直接消费的 duration 型 env 键」的读取口径(切片 E3+E4 的 // `CHANNEL_EVENT_*` 三个键是当前唯一调用方,design/26 §4.4)。 // // 为什么是读取函数,而不是给 Config 加字段: // // - Config 的反射解码只给出**值本身**,承载不了「未配置(用默认)」与「显式 ≤0 // (关闭)」必须分开的三态语义 —— 而渠道事件 GC 的关闭判据正是 ≤0(design/26 §4.4 // 写死「≤0 = 关闭」)。若用零值代表「未配置」,一次漏配就会把清扫关掉; // - 直接 `os.Getenv` 会让**只写在 `.env` 里**的声明被静默忽略:`.env` 由 parseDotEnv // 读进独立 map,并不写回进程环境(与 LoadContextBudgetRatio / LoadOwnerIdentityInjection // 同一理由,「配了却不生效」是被本项目点名禁止的失效模式)。 // // 因此这里复用**同一套优先级**(进程环境 > `.env` > 默认值)与**同一份 `.env` 解析**, // 两种写法都真实生效。envPath 为空表示不看 `.env`(与 Load 的约定一致)。 import ( "fmt" "strings" "time" ) // LoadDurationEnv 读取一个 duration 型 env 键(Go duration 文本,如 `30s` / `1h` / // `720h`,接受负值),按「进程环境 > .env > def」取值。键未配置或值为空白(`KEY=`) // 时一律返回 def —— 「没写」与「写了空」都当没配,绝不把空串当成一个合法取值。 // // 返回的 error **只承载「配了但读不懂」**:此时返回值仍是 def(调用方据此回落), // 但必须记一条带原始取值的 WARN 并继续 —— 非法配置不许静默失效(AGENTS §10)。 func LoadDurationEnv(envPath, key string, def time.Duration) (time.Duration, error) { fromEnv := map[string]string{} if envPath != "" { m, err := parseDotEnv(envPath) if err != nil { return def, fmt.Errorf("config: read %s: %w", envPath, err) } fromEnv = m } raw := strings.TrimSpace(lookupRaw(key, "", fromEnv)) if raw == "" { return def, nil } value, err := time.ParseDuration(raw) if err != nil { return def, fmt.Errorf("config: %s=%q 不是合法的 duration(如 30s / 1h / 720h): %w", key, raw, err) } return value, nil }