1. 22 Apr, 2026 2 commits
    • erio's avatar
      refactor(channel-monitor): tighten runner lifecycle + add unit tests · c46744f3
      erio authored
      - pool 改在 NewChannelMonitorRunner 构造时初始化,消除 Start 在 mu 内
        赋值、fire/Stop 在 mu 外读取的竞态隐患
      - Schedule 在 !started 时由静默 return 改为 slog.Warn,错过的调度可见
      - Schedule 在 interval<=0 时升为 slog.Error:Create/Update validateInterval
        已保证不可达,真触发即数据/校验链 bug
      - 抽出 monitorRunnerSvc 内部接口(仅 ListEnabledMonitors+RunCheck),
        生产 *ChannelMonitorService 自然满足;runner 单元测试可注入轻量 stub
      - 新增 channel_monitor_runner_test.go(10 个用例,//go:build unit):
        覆盖 Schedule/Unschedule/Start/Stop 生命周期、in-flight 槽对称释放、
        Stop 等待正在执行的 RunCheck 退出(无游离 goroutine)
      
      启动失败的恢复策略:保持现状(log+return)。CLAUDE.md 明确"配置应保证启动
      成功(必填项校验+正确数据校验)",validate{Provider,Interval,Endpoint,
      APIKey,PrimaryModel} 已在 Create/Update 全部覆盖;DB 不可用是基础设施问题,
      不该靠应用层无限重试兜底。
      c46744f3
    • erio's avatar
      refactor(channel-monitor): event-driven scheduler + sidebar cleanup · c2f9ad7a
      erio authored
      后端 - ChannelMonitorRunner 重写为事件驱动调度
      - 删除 5 秒轮询架构(每次 ListEnabled + listDueForCheck 全表扫描),
        改为每个 enabled monitor 一个独立 goroutine + ticker(按各自 IntervalSeconds)
      - 新增 MonitorScheduler 接口,service 通过 setter 注入避免依赖环
      - ChannelMonitorService.Create/Update/Delete 直接回调 scheduler.Schedule/Unschedule
      - runner.Start 一次性加载所有 enabled monitor 建立任务表
      - 新建/启用立即触发首次检测,禁用/删除即时撤销 ticker
      - 保留 inFlight 去重 + pond 池并发上限 + 全局开关每次 fire 实时校验
      - 删除 listDueForCheck / monitorTickerInterval / monitorListDueTimeout
      
      前端 - 可用渠道改为用户级菜单
      - 从 adminNavItems 移除 /available-channels(admin 主菜单不再重复出现)
      - buildSelfNavItems 始终包含可用渠道入口,普通用户主菜单和
        管理员"我的账户"区都能看到
      c2f9ad7a
  2. 21 Apr, 2026 1 commit
    • erio's avatar
      feat(channel-monitor): aggregate history to daily rollups + soft delete · 8cf83c98
      erio authored
      明细只保留 1 天,超过 1 天聚合到新表 channel_monitor_daily_rollups(按
      monitor_id/model/bucket_date 维度),聚合保留 30 天。两张表都用 SoftDeleteMixin
      软删除(DELETE 自动改为 UPDATE deleted_at = NOW())。
      
      聚合 + 清理任务由 OpsCleanupService 的 cron 统一调度,与运维监控的清理共享
      schedule(默认 0 2 * * *)和 leader lock。ChannelMonitorRunner 的 cleanupLoop
      被移除,只保留 dueCheckLoop。
      
      读取路径 ComputeAvailability* 改为 UNION 明细(今天 deleted_at IS NULL)+
      聚合(过去 windowDays 天 deleted_at IS NULL),SUM(ok)/SUM(total) 自然加权
      计算可用率,AVG latency 用 SUM(sum_latency_ms)/SUM(count_latency)。
      
      watermark 表 channel_monitor_aggregation_watermark 单行(id=1),记录
      last_aggregated_date,重启后从该日期 +1 继续聚合,首次为 nil 则从
      today - 30d 开始回填,单次最多 35 天上限避免长事务。
      
      raw SQL 的 ListLatestPerModel / ListLatestForMonitorIDs / ListRecentHistoryForMonitors
      都补上 deleted_at IS NULL 过滤(SoftDeleteMixin interceptor 只对 ent query 生效)。
      
      bump version to 0.1.114.28
      
      GroupBadge 在 MonitorKeyPickerDialog 中复用平台主题色 + 倍率/专属倍率
      (顺手优化)。
      8cf83c98
  3. 20 Apr, 2026 2 commits
    • erio's avatar
      feat(channel-monitor): add feature switch settings + fix extra_models save · 7da51240
      erio authored
      Settings:
      - New "功能开关" tab between 通用设置 and 安全与认证
      - ChannelMonitorEnabled toggle: runner skips scheduling when false,
        user-facing list returns empty
      - ChannelMonitorDefaultIntervalSeconds (15-3600): pre-fills interval
        when creating a new monitor; each monitor can still override
      
      Bug fix:
      - ModelTagInput now commits pending input on blur, not just Enter/Tab.
        Previously clicking "save" with an un-Enter'd extra model would drop
        the value (DB stored extra_models=[] even when user typed entries).
      
      Backend:
      - domain_constants: SettingKeyChannelMonitor{Enabled,DefaultIntervalSeconds}
      - SettingService.GetChannelMonitorRuntime: lightweight getter used by
        runner tick + user handler per-request (fail-open on DB error)
      - Runner tickDueChecks: bails early when feature disabled
      - ChannelMonitorUserHandler: checks feature flag before serving
      - Comment on runner doc: scheduler state is implicit (every tick re-reads
        ListEnabled from DB), so CRUD ops on monitors self-maintain the schedule
      
      Bump VERSION to 0.1.114.25
      7da51240
    • erio's avatar
      feat(monitor): admin channel monitor MVP with SSRF protection and batch aggregation · 20a4e418
      erio authored
      新增 admin「渠道监控」模块(参考 BingZi-233/check-cx),独立于现有 Channel 体系。
      admin 配置 + 后台定时调用上游 LLM chat completions 健康检查 + 所有登录用户只读可见。
      
      后端:
      - ent: channel_monitor + channel_monitor_history(AES-256-GCM 加密 api_key)
      - service 按职责拆分:service/aggregator/validate/checker/runner/ssrf
      - provider strategy map 替代 switch(openai/anthropic/gemini)
      - repository batch 聚合(ListLatestForMonitorIDs + ComputeAvailabilityForMonitors)消除 N+1
      - runner: ticker(5s) + pond worker pool(5) + inFlight 防并发 + TrySubmit 防雪崩
        + 凌晨 3 点 cron 清理 30 天历史
      - SSRF 防护:强制 https + 私网/loopback/云元数据 IP 拒绝(127/8、10/8、172.16/12、
        192.168/16、169.254/16、100.64/10、::1、fc00::/7、fe80::/10)+ DialContext
        在 socket 层防 DNS rebinding
      - API key sanitize:擦除 url.Error 与上游响应 body 中的 sk-/sk-ant-/AIza/JWT 模式
      - APIKeyDecryptFailed 标志位 + 单 monitor 路径检测,避免空 key 调用上游
      
      handler:
      - admin: CRUD + 手动触发 + 历史接口(api_key 脱敏)
      - user: 只读列表 + 状态详情(去除 api_key/endpoint)
      - ParseChannelMonitorID 共用 + dto.ChannelMonitorExtraModelStatus 共用
      
      前端:
      - 路由 /admin/channels/{pricing,monitor} + /monitor(用户只读)
      - AppSidebar 父项 expandOnly 支持
      - ChannelMonitorView 拆为 8 个子组件 + ChannelStatusView 拆出 detail dialog
      - composables/useChannelMonitorFormat + constants/channelMonitor 共享
      - i18n monitorCommon namespace 消除 admin/user 两 view 重复
      
      合规:所有文件符合 CLAUDE.md(Go ≤ 500 行 / Vue ≤ 300 行 / 函数 ≤ 30 行)
      CI: go build / gofmt / golangci-lint(0 issues) / make test-unit / pnpm build 全绿
      20a4e418