定义应用最小配置结构、默认值、加载入口和校验规则。
AppConfigload_default_configload_config_from_json_strvalidate_config
当前顶层字段:
timezone: Stringplatforms: Vec<String>(fixture 模式)schedule: ScheduleConfigrss_feeds: Vec<RssFeedConfig>(HTTP 模式)hotlist_apis: Vec<HotlistApiConfig>(HTTP 模式)storage: StorageConfigai_analysis: AiAnalysisConfignotification: NotificationConfigselection: SelectionConfig
必填字段:
timezone
默认值字段:
timezone默认值为Asia/Shanghaiplatforms默认值为空数组schedule.collect / analyze / push默认值均为trueschedule.window默认值为nullrss_feeds和hotlist_apis默认值为空数组storage默认值为StorageConfig::default()ai_analysis默认值为AiAnalysisConfig::default()notification默认值为NotificationConfig::default()selection默认值为SelectionConfig::default()
可延后字段:
- 输出目标与输出开关
- 更复杂的配置兼容层
当前字段:
schedule.collect: boolschedule.analyze: boolschedule.push: boolschedule.window.start_hour: u8schedule.window.end_hour: u8schedule.cooldown_minutes: u64?schedule.weekday.collect/analyze/push: Option<bool>schedule.weekday.window: Option<ScheduleWindowConfig>schedule.weekend.collect/analyze/push: Option<bool>schedule.weekend.window: Option<ScheduleWindowConfig>
当前语义:
collect表示是否允许进入抓取阶段analyze表示是否允许进入分析阶段push表示是否允许进入推送阶段
当前保留边界:
- 时区相关调度窗口
- 工作日 / 周末覆盖规则
- 冷却周期等带状态依赖的复杂调度表达
非法配置的错误语义:
- 当前调度字段是布尔值,不引入额外校验错误
- 后续扩展复杂调度配置时,必须继续复用
TrendRadarError::InvalidConfig - 错误消息需要包含具体字段名和触发条件
热榜平台列表:
platforms表示热榜来源标识列表- 当前允许空数组,空数组不视为非法配置
RSS 订阅列表:
rss_feeds包含RssFeedConfig { source_id, url }结构体数组- 每个 RSS 订阅源需要
source_id(标识)和url(feed 地址) - 空数组不视为非法配置
热榜 API 列表:
hotlist_apis包含HotlistApiConfig { platform_id, url }结构体数组- 每个热榜源需要
platform_id(平台标识)和url(API 地址) - 空数组不视为非法配置
输出开关与输出目标:
- 当前尚未进入实现
- 在进入
report与app集成前,不阻塞config最小闭环
通知配置:
notification.enabled: boolnotification.sinks: NotificationSinkConfig[]notification.sinks[].kind: "webhook" | "feishu" | "dingtalk" | "wecom" | "slack" | "discord" | "ntfy"notification.sinks[].url: Stringnotification.webhook_url: Option<String>notification.feishu_webhook_url: Option<String>notification.dingtalk_webhook_url: Option<String>notification.wecom_webhook_url: Option<String>notification.discord_webhook_url: Option<String>notification.ntfy_topic_url: Option<String>- 所有通知渠道字段缺失时默认回落为
None notification.sinks与旧平铺字段可同时存在,app层会合并它们并避免同类型同 URL 重复发送
结果选取策略:
selection.high_rank_fallback_max_rank: Option<u32>selection.min_items_per_source: Option<usize>selection.min_items_per_domain: Option<usize>high_rank_fallback_max_rank允许关键词未命中时仍保留 rank 不高于阈值的高热度条目min_items_per_source用于保证每个来源至少保留指定数量的条目min_items_per_domain用于保证每个领域至少保留指定数量的条目,领域由标题和来源信号做启发式分类
AI 分析配置:
ai_analysis.enabled: boolai_analysis.provider: Stringai_analysis.timeout_secs: u64ai_analysis.retry_attempts: u8ai_analysis.max_items: usizeai_analysis.prompt: Option<String>ai_analysis.model: Option<String>ai_analysis.base_url: Option<String>ai_analysis.api_key: Option<String>ai_analysis.api_key_env: Option<String>mockprovider 仅依赖max_items与可选promptopenai-compatibleprovider 额外依赖model、base_url和api_key/api_key_env
解析错误:
- JSON 解析失败时返回
TrendRadarError::InvalidConfig - 错误消息前缀固定为
failed to parse config json:
校验错误:
timezone为空时返回TrendRadarError::InvalidConfig- 当前错误消息为
timezone must not be empty - 非法时区字符串返回
TrendRadarError::InvalidConfig - 当前错误消息为
timezone must be a valid IANA timezone
错误消息要求:
- 首版至少要包含字段名或解析失败原因
- 不要求与旧系统错误文本完全一致
当前不兼容旧配置文件结构:
- Rust 首版不追求完整兼容旧配置
- 只保留主链路所需的最小配置子集
迁移策略:
- 先在契约文档中显式声明“保留 / 延后”的字段范围
- 后续如需兼容旧配置,应通过明确的映射规则扩展,而不是在
AppConfig中一次性堆入所有历史字段
fixture:
- fixtures/system/config/minimal-valid.json
- fixtures/system/config/invalid-empty-timezone.json
- fixtures/system/config/invalid-unknown-timezone-window.json
- fixtures/system/config/minimal-valid-http.json
测试:
cargo test -p trendradar-configcargo test -p trendradar-app --test config_to_bootstrap
快照:
- 当前不需要独立快照
- Wave 0 通过 fixture 驱动测试锁定行为
-
顶层配置是否拆为模块化子结构体
-
配置加载是否只保留 JSON,还是预留 TOML / YAML 当前已进入的最小窗口表达:
-
schedule.window为可选字段 -
start_hour/end_hour代表本地时区小时窗口 -
当前使用半开区间
[start_hour, end_hour)语义 -
当
start_hour > end_hour时表示跨午夜窗口
当前校验规则:
start_hour与end_hour必须都在0..=23start_hour与end_hour不能相等- 非法样例已覆盖相等小时与越界小时两类失败路径