目标:新增一个“服务状态”监控页面(参考你提供的截图样式),用于监控 渠道(Channel) 与 模型(Model) 的服务可用性、成功率、延迟与近一段时间的趋势。
覆盖范围:
aiapi/(后端 C++ Drogon 项目):接口设计 + 数据库设计 + 采集/聚合逻辑aiapi_web/(前端 React/Vite/TS):页面信息架构 + 组件设计 + API/类型设计
当前项目已具备:
- 渠道管理(
channelManager,channelDbManager) - 模型列表接口(OpenAI 兼容
GET /chaynsapi/v1/models) - 错误监控与请求/错误统计(前端
ErrorMonitor组件已接入/aichat/metrics/*)
我们希望新增一个“服务状态”页,提供类似截图的:
- 顶部:最近更新时间 + 总体 API 状态(正常/异常/未知)
- 列表:按渠道/模型展示
- 可用率(%)
- 请求数、成功数
- 近 N 个时间桶的小柱状图(成功/失败或仅成功率)
- 右侧状态徽章:正常/异常/未知
- 渠道状态监控:按渠道统计请求量/成功量/可用率,并给出状态。
- 模型状态监控:按模型统计请求量/成功量/可用率,并给出状态(可按 provider / channel 维度细分)。
- 趋势可视化:提供近一段时间(例如 1h/6h/24h/7d)的小时间序列。
- 可扩展:后续可加入主动探测(probe)、更多维度(client_type、api_kind、stream)。
- 不做复杂告警系统(短信/邮件/钉钉),但设计保留扩展点。
- 不做 Prometheus/Grafana 集成(已有
/metrics可后续对接)。 - 不做实时 WebSocket 推送,先用轮询刷新。
我们提供两种数据来源,并在接口层统一输出:
- 被动观测(推荐优先):从网关自身的请求日志/事件中统计“真实流量”成功率与延迟。
- 优点:反映真实用户体验。
- 缺点:无流量时是“未知”。
- 主动探测(可选增强):定时对渠道/模型发起轻量探测请求(例如最小 prompt 或
/models),记录探测成功/失败与 RTT。
- 优点:无流量也能判断“活着/死了”。
- 缺点:会产生额外调用与成本。
本次设计文档:
- 接口/DB 以“被动观测 + 可选主动探测”双轨设计。
- 前端默认展示被动观测指标;如果启用 probe,可在 UI 增加“探测状态”。
建议新增:
src/controllers/ServiceStatus.cc/.h(Drogon Controller)src/dbManager/monitor/(新 DB manager)serviceStatusDbManager.cpp/.h
src/monitor/(可选:聚合/探测逻辑)StatusAggregator.cpp/.hStatusProbeScheduler.cpp/.h(可选)
与现有结构保持一致:controller 负责路由与参数校验;dbManager 负责 SQL;业务逻辑独立。
假设使用 PostgreSQL(项目已有
db_clients支持)。
如果系统已经有“请求事件/错误事件”表,优先复用;否则新增轻量明细表用于后续聚合。
表:aichat_request_events(建议)
id bigserial primary keyts timestamptz not nullrequest_id textchannel_id int(可空;取决于请求是否命中渠道)channel_name textprovider textmodel textapi_kind text(chat/responses/models…)client_type textstream booleanhttp_status intlatency_ms intsuccess boolean not nullerror_type text(失败时)error_message text(失败时,注意长度)
索引:
(ts)(ts, channel_id)(ts, model)(request_id)
若已有类似记录表,可只新增聚合表。
用于快速查询趋势与列表。
表:aichat_status_buckets
bucket_start timestamptz not null(UTC,桶起始)bucket_minutes int not null(例如 1/5/15/60/360/1440)entity_kind text not null('API' | 'CHANNEL' | 'MODEL')entity_key text not null- API: 'api'
- CHANNEL:
channel:{id}或channel:{name}(建议用 id) - MODEL:
model:{provider}:{model}(必要时加入 channel)
entity_name text not nullrequests int not null default 0success int not null default 0fail int not null default 0avg_latency_ms int(可空)p95_latency_ms int(可选扩展)last_http_status int(可选扩展)meta jsonb(可选:维度扩展)
主键/唯一:
- unique
(bucket_start, bucket_minutes, entity_kind, entity_key)
索引:
(entity_kind, entity_key, bucket_minutes, bucket_start desc)
表:aichat_probe_events
id bigserial primary keyts timestamptz not nullentity_kind text not null(CHANNEL|MODEL|API)entity_key text not nullok boolean not nulllatency_ms inthttp_status interror textdetail jsonb
索引:
(entity_kind, entity_key, ts desc)
是否启用 probe:通过配置
custom_config.service_status.probe_enabled控制。
状态枚举:
OK(正常)DEGRADED(波动/部分失败)DOWN(严重失败)UNKNOWN(无数据)
默认阈值(可配置):
ok_threshold = 0.99degraded_threshold = 0.95min_requests_for_confidence = 20(低于此值可降权或显示 UNKNOWN/DEGRADED)stale_after_seconds = 180(例如 3 分钟内无新桶则认为数据过期)
判定:
- 有请求:
availability = success/requests-
= ok_threshold → OK
-
= degraded_threshold → DEGRADED
- else → DOWN
-
- 无请求:
- 若启用 probe 且最近 probe ok → OK
- 若启用 probe 且最近 probe fail → DOWN
- 否则 → UNKNOWN
每次 API 调用直接对 aichat_request_events 按时间桶聚合。
- 优点:无需后台任务
- 缺点:数据量大时慢
通过 drogon 定时器每分钟/每5分钟写入 aichat_status_buckets。
- 例如每 60 秒聚合上一分钟数据,写入 bucket_minutes=1
- 同时可滚动生成 5m/15m/1h 桶(或前端只用 1m,后端返回时可再聚合)
推荐实现:
- 先做
bucket_minutes=1(粒度最高) - API 支持
interval=1m|5m|15m|1h|6h|1d,返回时由 SQLdate_trunc+GROUP BY合并
命名风格建议与现有
/aichat/metrics/...一致:新增/aichat/status/...。
GET /aichat/status/summary
Query:
from(UTC "YYYY-MM-DD HH:MM:SS")to(UTC "YYYY-MM-DD HH:MM:SS")interval(可选,默认1m)
Response:
{
"from": "2026-02-03 00:00:00",
"to": "2026-02-03 01:00:00",
"updated_at": "2026-02-03 01:00:05",
"status": "OK",
"availability": 1.0,
"requests": 60,
"success": 60,
"avg_latency_ms": 230,
"series": [
{"bucket_start":"2026-02-03 00:00:00","requests":1,"success":1,"fail":0}
]
}GET /aichat/status/channels
Query:
from,to,interval(同上)include_series(bool,默认 true;用于列表小柱状图)
Response:
{
"from": "...",
"to": "...",
"updated_at": "...",
"items": [
{
"channel_id": 1,
"channel_name": "gemini-business2api",
"provider": "gemini",
"status": "OK",
"availability": 1.0,
"requests": 60,
"success": 60,
"fail": 0,
"avg_latency_ms": 210,
"series": [
{"bucket_start":"...","requests":2,"success":2,"fail":0}
]
}
]
}GET /aichat/status/channels/{channel_id}
返回包含更丰富维度:top models、最近错误、p95 latency 等。
GET /aichat/status/models
Query:
from,to,intervalprovider(可选)channel_id(可选:模型在不同渠道可表现不同)
Response:
{
"items": [
{
"provider": "gemini",
"model": "Gemini 2.5 Flash",
"status": "UNKNOWN",
"availability": 1.0,
"requests": 0,
"success": 0,
"fail": 0,
"avg_latency_ms": null,
"series": []
}
]
}POST /aichat/status/probe/run
Body:
{ "entity_kind": "CHANNEL", "entity_key": "channel:1" }用于管理员手动触发一次探测,返回 probe 结果。
建议:
- 这些监控接口仅供管理后台使用。
- 与现有认证机制对齐(若已有 token/session 体系则复用)。
- 若暂时没有统一 auth:可在 Nginx 层或后端加一个简单
X-Admin-Token(配置项),后续替换。
新增左侧导航:
- “监控状态 / 服务状态”
建议路由:
/monitor/status(服务状态)- 未来扩展:
/monitor/errors(已有 ErrorMonitor 可迁移到此)
页面标题区:
- 标题:服务状态
- 子标题:最近更新:YYYY-MM-DD HH:mm:ss
- 右侧:刷新按钮、时间范围选择、间隔选择(可复用 ErrorMonitor 的 timeRange / interval 选择器)
模块 1:API 服务总览卡片
- 状态徽章:正常/异常/未知
- 指标:可用率、请求、成功、平均延迟
- 趋势:小柱状图(requests/success/fail 或 successRate)
模块 2:渠道状态列表
- 每行一个 channel
- 左:名称
- 中:可用率 / 请求 / 成功
- 下:小柱状图
- 右:状态徽章
模块 3:模型状态列表
- 每行一个 model(可按 provider 分组折叠)
- 同样指标与趋势
必选:
- 时间范围:最近 1h / 6h / 24h / 7d
- 刷新:手动刷新
可选:
- 自动刷新:每 30s/60s
- 筛选:provider、channel、状态(OK/DOWN/UNKNOWN)
- 展开详情:点击行进入详情 drawer/modal
- 页面加载:并发请求
GET /aichat/status/summaryGET /aichat/status/channelsGET /aichat/status/models
- 轮询刷新:可配置 60s;刷新时保留用户筛选与滚动位置。
建议新增:
export type ServiceHealthStatus = 'OK' | 'DEGRADED' | 'DOWN' | 'UNKNOWN';
export interface StatusBucket {
bucket_start: string; // backend UTC string
requests: number;
success: number;
fail: number;
avg_latency_ms?: number | null;
}
export interface ApiStatusSummary {
from: string;
to: string;
updated_at: string;
status: ServiceHealthStatus;
availability: number;
requests: number;
success: number;
avg_latency_ms?: number | null;
series: StatusBucket[];
}
export interface ChannelStatusItem {
channel_id: number;
channel_name: string;
provider?: string;
status: ServiceHealthStatus;
availability: number;
requests: number;
success: number;
fail: number;
avg_latency_ms?: number | null;
series?: StatusBucket[];
}
export interface ModelStatusItem {
provider?: string;
model: string;
channel_id?: number;
status: ServiceHealthStatus;
availability: number;
requests: number;
success: number;
fail: number;
avg_latency_ms?: number | null;
series?: StatusBucket[];
}建议在 src/components/ 新增:
ServiceStatusMonitor.tsx(页面容器)ServiceStatusMonitor.css- 子组件:
StatusHeader(标题+更新时间+工具栏)StatusRow(单行:名称+指标+徽章+mini chart)MiniBars(小柱状图,复用/改造SimpleBarChart支持 success/fail 堆叠或 successRate)
状态徽章:
- OK:绿色“正常”
- DEGRADED:橙色“波动”
- DOWN:红色“异常”
- UNKNOWN:灰色“未知”
-
每条列表项:
- 第一行:名称(左)+ 状态 pill(右)
- 第二行:可用率 100% 请求 X 成功 Y
- 第三行:mini bar chart(固定高度、固定桶数,如 60 个 1m 桶)
-
无数据:灰色 bars + “未知”
-
0 请求:展示请求 0、成功 0,可用率默认 100% 但状态 UNKNOWN(避免误导)
前端沿用我们在 ErrorMonitor 中引入的格式:
- 后端用 UTC 字符串
YYYY-MM-DD HH:MM:SS - 前端保留 ISO Date 并通过 helper 转换为后端格式
前端 timeRange → from/to:
- 1h:
to=now,from=now-1h - 6h:
from=now-6h - 24h:
from=now-24h - 7d:
from=now-7d
推荐默认:
- 1h → 1m(60桶)
- 6h → 5m(72桶)
- 24h → 15m(96桶)
- 7d → 1h(168桶)
- 列表页要快:建议服务端返回已聚合数据,避免前端做重计算。
include_series=false可用于移动端/低带宽。- 后端聚合表可以保留 30 天 1m 桶,超过降采样(例如 15m/1h),或使用分区表。
- 后端:实现
GET /aichat/status/summary|channels|models(基于现有请求/错误数据或临时聚合)。 - 前端:新页面 + 列表 UI + 手动刷新。
- 后端:引入
aichat_status_buckets聚合表与定时聚合。 - 前端:自动刷新、筛选、详情。
- 后端:主动 probe 任务与
aichat_probe_events。 - 前端:展示“探测状态/最近探测时间”。
- 模型维度:模型状态是"全局模型"(跨渠道)还是"模型×渠道"?
- 数据来源:目前后端是否已有"请求成功/失败"持久化表?(若没有,需要新增事件表或从日志解析)
- 权限:管理后台是否已有统一鉴权?若没有,是否接受临时
X-Admin-Token? - 是否启用主动探测:默认关闭(节省成本)还是打开?
用户回复:
- 模型状态维度按照【模型×渠道】
- 现有数据来源,不确定,请检查
- 管理端暂无用户管理,暂时不实现鉴权
- 编写主动探测逻辑,但默认关闭(仅真实流量)
经过代码扫描,后端已有请求/错误统计数据落库:
| 表名 | 用途 | 关键字段 |
|---|---|---|
request_agg_hour |
请求聚合(按小时桶) | bucket_start, provider, model, client_type, api_kind, stream, http_status, count |
error_agg_hour |
错误聚合(按小时桶) | bucket_start, severity, domain, type, provider, model, client_type, api_kind, stream, http_status, count |
error_event |
错误明细 | ts, severity, domain, type, provider, model, client_type, api_kind, stream, http_status, request_id, message, ... |
GenerationService.cpp:334中的recordRequestCompletedStat()在请求完成时调用ErrorStatsService::recordRequestCompleted()ErrorStatsService.cpp:193将数据推入队列,后台线程 flush 到request_agg_hour表- 错误/警告事件通过
recordError()/recordWarn()写入error_event+error_agg_hour
| 问题 | 影响 |
|---|---|
缺少 channel_id / channel_name |
无法按渠道聚合;目前只有 provider(如 "chaynsapi"),但一个 provider 可能对应多个 channel |
缺少 latency_ms |
无法计算平均延迟 |
缺少 success 布尔标记 |
需要通过 http_status 推断(2xx/3xx 视为成功) |
为支持"模型×渠道"维度与延迟统计,需要:
- 扩展
RequestCompletedData结构:增加channelId,channelName,latencyMs字段 - 扩展
request_agg_hour表:增加channel_id,channel_name,total_latency_ms,success_count,fail_count列 - 在
GenerationService中采集:记录请求开始时间,完成时计算延迟;从 session 中获取 channel 信息 - 新增查询方法:
queryStatusByChannel(),queryStatusByModel()支持按渠道/模型聚合
- 后端:新增 3 个 status API(
/aichat/status/summary|channels|models),基于现有request_agg_hour+error_agg_hour聚合- 暂时用
provider代替channel(因为目前只有一个 provider "chaynsapi") - 成功/失败通过
http_status推断
- 暂时用
- 前端:新增
ServiceStatusMonitor页面 + 路由
- 后端:扩展
RequestCompletedData+request_agg_hour表,增加channel_id,channel_name,latency_ms - 后端:在
GenerationService中采集 channel 信息与延迟 - 前端:展示延迟指标
- 后端:实现
StatusProbeScheduler,定时对渠道/模型发起探测 - 后端:新增
aichat_probe_events表 - 前端:展示探测状态