主人 2026-10-07:「.env 只做兜底…优先从面板配置上取」+「频率必须调…或者动态
分配,比如白天间隔短一点 晚上到了静默时期稍微长一点」。
- memory-tidy.json 新增四键:intervalSec / quietIntervalSec / quietStartHour /
quietEndHour(*int + 指针判定 => 显式 0 是合法值、缺位才回落)
- 优先级逐键三档:面板 > env(既有键,未增删)> 代码默认;坏值不覆盖 + WARN,
且不启动失败
- 动态间隔由纯函数 intervalAt(at) 决定,调度侧每一拍现算 => 跑完按当时时段重算
- 🔴 语义变更(有意,代码注释已登记,待补设计修订):安静时段从「唯一可跑时段」
变成「降频时段」—— 旧的「安静窗外一律 continue」被删,否则 intervalSec 永远
是空壳(白天唤醒必被跳过);窗口仍决定档位与补跑判据
- 6 个变异(含「加回旧的那道门」=> 端到端用例红)
88 lines
4.5 KiB
Go
88 lines
4.5 KiB
Go
package memory
|
||
|
||
import (
|
||
"log/slog"
|
||
|
||
"git.yeij.top/AskaEth/Cyrene_v2/internal/config/runtimeconfig"
|
||
"git.yeij.top/AskaEth/Cyrene_v2/internal/logger"
|
||
)
|
||
|
||
// 本文件是**整理频率**的解析点:把 `$DATA_DIR/config/memory-tidy.json` 的四个调度
|
||
// 旋钮叠到 env 侧的 TidyConfig 上,实现主人 2026-10-07 的两条口径:
|
||
//
|
||
// - 「**`.env` 只做兜底用** —— 后端自动创建独立的面板配置文件之后就走面板配置
|
||
// 文件,这些键优先从面板配置上取」⇒ 逐键 **面板 > `.env` > 代码默认**;
|
||
// - 「**频率必须调**,可以调到 1 小时或者三小时,**或者动态分配**」⇒ 周期分两档
|
||
// (非安静时段的 intervalSec / 安静时段的 quietIntervalSec,见 intervalAt)。
|
||
//
|
||
// 为什么解析放在 memory 而不是 runtimeconfig:runtimeconfig 只负责**文件形状与
|
||
// 读取**(它的头注释把这条边界写死了),而「缺位/坏值怎么回落」属于 TidyConfig 的
|
||
// 归一化语义 —— 与 normalizeTidyConfig 同一层,因此两者可以一起被单测覆盖。
|
||
//
|
||
// 为什么不是「读盘时逐键容错」:整份文件解不开(含某键类型写错)时**四个键一起**
|
||
// 回落 `.env` 并记 WARN,这是本文件家族既有的粒度(见
|
||
// runtimeconfig.LoadMemoryTidyTuning 与它的用例③);本文件处理的是**类型正确但值
|
||
// 不可用**(非正数)那一档。
|
||
|
||
// ApplyTidyScheduleTuning 把面板调度旋钮叠到 env 侧的 TidyConfig 上,返回叠加后的
|
||
// 配置(**未归一化**:调用方随后交给 NewTidier / normalizeTidyConfig 填代码默认值,
|
||
// 与组合根的既有顺序一致)。
|
||
//
|
||
// 逐键独立,三档优先级在四个键上各自成立:
|
||
//
|
||
// 键 面板(memory-tidy.json) 下一档(.env / 派生) 代码默认
|
||
// intervalSec intervalSec MEMORY_TIDY_INTERVAL_SEC 86400
|
||
// quietIntervalSec quietIntervalSec 派生:= intervalSec 同左
|
||
// quietStartHour quietStartHour MEMORY_TIDY_QUIET_START_HOUR 23
|
||
// quietEndHour quietEndHour MEMORY_TIDY_QUIET_END_HOUR 7
|
||
//
|
||
// 「面板缺位 ⇒ 用 `.env`」这一步在实现上是**不覆盖**:cfg 进函数时已经带着 env 的
|
||
// 值(TidyConfigFromConfig),面板没写就原样留着;「`.env` 也缺位 ⇒ 代码默认」由
|
||
// 随后的 normalizeTidyConfig 收尾(env 的 default 标签在 config.Load 那一层已经
|
||
// 兜过一道,两道都缺时落到 defaultTidyIntervalSec / 23-07)。
|
||
//
|
||
// 坏值(非正数的秒数)**绝不写进 cfg**:回落下一档 + 记一条 WARN。一个 ≤0 的周期会
|
||
// 让心跳退化成空转,因此它不是「没配」而是坏配置 —— 但也不许因此让启动失败。
|
||
func ApplyTidyScheduleTuning(cfg TidyConfig, panel runtimeconfig.MemoryTidyTuning, log *slog.Logger) TidyConfig {
|
||
log = logger.WithSource(baseTidyLog(log), "memory/tidy")
|
||
|
||
if sec := panelPositiveSec(panel.IntervalSec, "intervalSec", log); sec > 0 {
|
||
cfg.IntervalSec = sec
|
||
// Interval 与 IntervalSec 同时归零:两者是「同一旋钮的两种写法」
|
||
// (设置了 Interval 时忽略 IntervalSec),只改一个会让旧值报复性生效。
|
||
cfg.Interval = 0
|
||
}
|
||
if sec := panelPositiveSec(panel.QuietIntervalSec, "quietIntervalSec", log); sec > 0 {
|
||
cfg.QuietIntervalSec = sec
|
||
cfg.QuietInterval = 0
|
||
}
|
||
// 钟点用「指针是否有值」而不是符号判据:**0 是合法钟点**(0-7 的凌晨窗口、
|
||
// 23-0 到午夜的窗口),因此显式写的 0 必须原样生效,缺位才回落 env。
|
||
if panel.QuietStartHour != nil {
|
||
cfg.QuietStartHour = *panel.QuietStartHour
|
||
}
|
||
if panel.QuietEndHour != nil {
|
||
cfg.QuietEndHour = *panel.QuietEndHour
|
||
}
|
||
return cfg
|
||
}
|
||
|
||
// panelPositiveSec 解析一个**正数秒数**旋钮,返回 0 表示「不覆盖」(调用方原样留着
|
||
// env 值)。三种情形分得很清,因为它们该有不同的观测形状:
|
||
//
|
||
// - `nil`(键没写)⇒ 0,**静默**:没配不是错,正是「`.env` 兜底」这一档;
|
||
// - 非正数(写了 0 或负数)⇒ 0 + **WARN**:它会被读成「一直跑」而不是「不跑」,
|
||
// 属于坏配置,必须留下痕迹(否则主人以为配了 0 就等于关了,实际相反);
|
||
// - 正数 ⇒ 原值。
|
||
func panelPositiveSec(v *int, key string, log *slog.Logger) int {
|
||
if v == nil {
|
||
return 0
|
||
}
|
||
if *v <= 0 {
|
||
log.Warn("memory-tidy.json 的调度旋钮值不可用,回落 .env(非正周期会让心跳空转)",
|
||
"key", key, "value", *v)
|
||
return 0
|
||
}
|
||
return *v
|
||
}
|