diff --git a/backend/internal/repository/channel_monitor_repo.go b/backend/internal/repository/channel_monitor_repo.go index 67dccd6c..800ee43b 100644 --- a/backend/internal/repository/channel_monitor_repo.go +++ b/backend/internal/repository/channel_monitor_repo.go @@ -297,41 +297,22 @@ func assignNullInt(dst **int, n sql.NullInt64) { // "可用" = status IN (operational, degraded)。 // // 数据来源:明细表只保留 1 天;窗口前其余天数走聚合表。 -// - raw = 今天(CURRENT_DATE 起)的未软删明细,按 model 累加 -// - rollup = [CURRENT_DATE - windowDays, CURRENT_DATE) 区间的聚合行 -// -// 总窗口为 "今天 + 过去 windowDays 天",比 windowDays 字面值大 1 天,但因为聚合 -// 是按整 UTC 日切的,这是聚合化无法避免的精度损失,且偏宽不偏窄(数据更全)。 +// 明细保留 30 天(monitorHistoryRetentionDays),窗口 <= 30 天时直接扫 histories, +// 精度到秒,避免与聚合表 UNION 带来的 UTC 日切精度损失。 func (r *channelMonitorRepository) ComputeAvailability(ctx context.Context, monitorID int64, windowDays int) ([]*service.ChannelMonitorAvailability, error) { if windowDays <= 0 { windowDays = 7 } const q = ` - WITH raw AS ( - SELECT model, - COUNT(*) AS total_checks, - COUNT(*) FILTER (WHERE status IN ('operational','degraded')) AS ok_count, - COALESCE(SUM(latency_ms) FILTER (WHERE latency_ms IS NOT NULL), 0) AS sum_latency_ms, - COUNT(latency_ms) AS count_latency - FROM channel_monitor_histories - WHERE monitor_id = $1 - AND checked_at >= CURRENT_DATE - GROUP BY model - ), - rollup AS ( - SELECT model, total_checks, ok_count, sum_latency_ms, count_latency - FROM channel_monitor_daily_rollups - WHERE monitor_id = $1 - AND bucket_date >= (CURRENT_DATE - $2::int) - AND bucket_date < CURRENT_DATE - ) SELECT model, - SUM(total_checks) AS total, - SUM(ok_count) AS ok, - CASE WHEN SUM(count_latency) > 0 - THEN SUM(sum_latency_ms)::float8 / SUM(count_latency) - ELSE NULL END AS avg_latency_ms - FROM (SELECT * FROM raw UNION ALL SELECT * FROM rollup) combined + COUNT(*) AS total, + COUNT(*) FILTER (WHERE status IN ('operational','degraded')) AS ok, + CASE WHEN COUNT(latency_ms) > 0 + THEN SUM(latency_ms) FILTER (WHERE latency_ms IS NOT NULL)::float8 / COUNT(latency_ms) + ELSE NULL END AS avg_latency_ms + FROM channel_monitor_histories + WHERE monitor_id = $1 + AND checked_at >= NOW() - ($2::int || ' days')::interval GROUP BY model ` rows, err := r.db.QueryContext(ctx, q, monitorID, windowDays) @@ -514,7 +495,7 @@ func clampTimelineLimit(n int) int { } // ComputeAvailabilityForMonitors 一次性计算多个监控在某个窗口内的每模型可用率与平均延迟。 -// 与单 monitor 版本同构:明细只覆盖今天,更早走聚合表 UNION 合并。 +// 明细保留 30 天,直接扫 histories(窗口 <= 30 天时无需聚合)。 func (r *channelMonitorRepository) ComputeAvailabilityForMonitors(ctx context.Context, ids []int64, windowDays int) (map[int64][]*service.ChannelMonitorAvailability, error) { out := make(map[int64][]*service.ChannelMonitorAvailability, len(ids)) if len(ids) == 0 { @@ -524,33 +505,16 @@ func (r *channelMonitorRepository) ComputeAvailabilityForMonitors(ctx context.Co windowDays = 7 } const q = ` - WITH raw AS ( - SELECT monitor_id, - model, - COUNT(*) AS total_checks, - COUNT(*) FILTER (WHERE status IN ('operational','degraded')) AS ok_count, - COALESCE(SUM(latency_ms) FILTER (WHERE latency_ms IS NOT NULL), 0) AS sum_latency_ms, - COUNT(latency_ms) AS count_latency - FROM channel_monitor_histories - WHERE monitor_id = ANY($1) - AND checked_at >= CURRENT_DATE - GROUP BY monitor_id, model - ), - rollup AS ( - SELECT monitor_id, model, total_checks, ok_count, sum_latency_ms, count_latency - FROM channel_monitor_daily_rollups - WHERE monitor_id = ANY($1) - AND bucket_date >= (CURRENT_DATE - $2::int) - AND bucket_date < CURRENT_DATE - ) SELECT monitor_id, model, - SUM(total_checks) AS total, - SUM(ok_count) AS ok, - CASE WHEN SUM(count_latency) > 0 - THEN SUM(sum_latency_ms)::float8 / SUM(count_latency) - ELSE NULL END AS avg_latency_ms - FROM (SELECT * FROM raw UNION ALL SELECT * FROM rollup) combined + COUNT(*) AS total, + COUNT(*) FILTER (WHERE status IN ('operational','degraded')) AS ok, + CASE WHEN COUNT(latency_ms) > 0 + THEN SUM(latency_ms) FILTER (WHERE latency_ms IS NOT NULL)::float8 / COUNT(latency_ms) + ELSE NULL END AS avg_latency_ms + FROM channel_monitor_histories + WHERE monitor_id = ANY($1) + AND checked_at >= NOW() - ($2::int || ' days')::interval GROUP BY monitor_id, model ` rows, err := r.db.QueryContext(ctx, q, pq.Array(ids), windowDays) diff --git a/backend/internal/service/channel_monitor_const.go b/backend/internal/service/channel_monitor_const.go index 768a432f..2fc45639 100644 --- a/backend/internal/service/channel_monitor_const.go +++ b/backend/internal/service/channel_monitor_const.go @@ -16,9 +16,10 @@ const ( // monitorDegradedThreshold 主请求成功但耗时超过该阈值视为 degraded。 monitorDegradedThreshold = 6 * time.Second // monitorHistoryRetentionDays 明细历史保留天数。 - // 明细只保留 1 天,超出由 SoftDeleteMixin 软删; - // 维护任务每天凌晨跑(由 OpsCleanupService 统一调度)。 - monitorHistoryRetentionDays = 1 + // 60s 默认间隔 * 30 天 ≈ 43200 行/monitor/model,一般部署总量 <= 2M 行, + // PG 无压力;所以直接保留完整明细一个月,可用率查询可以全走原始行不依赖聚合。 + // 聚合表 channel_monitor_daily_rollups 仍然保留,作为长期历史回填/降级查询的兜底。 + monitorHistoryRetentionDays = 30 // monitorRollupRetentionDays 日聚合保留天数。 // 日聚合行由 RunDailyMaintenance 在超过该窗口后软删。 monitorRollupRetentionDays = 30 diff --git a/backend/migrations/129_seed_claude_code_template.sql b/backend/migrations/129_seed_claude_code_template.sql new file mode 100644 index 00000000..d9b062c9 --- /dev/null +++ b/backend/migrations/129_seed_claude_code_template.sql @@ -0,0 +1,38 @@ +-- Migration: 129_seed_claude_code_template +-- 内置「Claude Code 伪装」请求模板,覆盖 Anthropic 上游对官方 CLI 客户端的所有验证项: +-- 1) User-Agent / X-App / anthropic-beta / anthropic-version 等头 +-- 2) system 数组首项与官方 system prompt 字面一致(Dice >= 0.5) +-- 3) metadata.user_id 满足 ParseMetadataUserID — 这里用 legacy 格式(user_<64hex>_account__session_<36char>) +-- 避免新版 JSON 字符串内嵌 JSON 在编辑器里出现一长串 \" 转义,便于用户阅读。 +-- +-- ON CONFLICT DO NOTHING:已部署环境(手动建过模板)跑此 migration 不会重复 / 覆盖。 +-- 用户可自行编辑后续覆盖此 seed;CC 升大版时再起一条 migration 提供新模板,不动用户的旧模板。 + +INSERT INTO channel_monitor_request_templates ( + name, provider, description, extra_headers, body_override_mode, body_override +) +VALUES ( + 'Claude Code 伪装', + 'anthropic', + '完整模拟 Claude Code 2.1.114 客户端:UA + anthropic-beta + system + metadata.user_id 全部对齐,绕过 Anthropic 上游 ''Claude Code only'' 限制(如 Max 套餐)。', + '{ + "User-Agent": "claude-cli/2.1.114 (external, sdk-cli)", + "X-App": "cli", + "anthropic-version": "2023-06-01", + "anthropic-beta": "claude-code-20250219,interleaved-thinking-2025-05-14,context-management-2025-06-27,prompt-caching-scope-2026-01-05,advisor-tool-2026-03-01", + "anthropic-dangerous-direct-browser-access": "true" + }'::jsonb, + 'merge', + '{ + "system": [ + { + "type": "text", + "text": "You are Claude Code, Anthropic''s official CLI for Claude." + } + ], + "metadata": { + "user_id": "user_0000000000000000000000000000000000000000000000000000000000000000_account_00000000-0000-0000-0000-000000000000_session_00000000-0000-0000-0000-000000000000" + } + }'::jsonb +) +ON CONFLICT (provider, name) DO NOTHING; diff --git a/frontend/src/components/admin/monitor/MonitorAdvancedRequestConfig.vue b/frontend/src/components/admin/monitor/MonitorAdvancedRequestConfig.vue index 24827316..fb503a49 100644 --- a/frontend/src/components/admin/monitor/MonitorAdvancedRequestConfig.vue +++ b/frontend/src/components/admin/monitor/MonitorAdvancedRequestConfig.vue @@ -38,12 +38,24 @@
- +
+ + +