节点与监控
本章覆盖 HyperFRP Panel 后端的节点查询与监控类接口,共 8 个:按地区分组的节点列表(含全局统计)、单节点详情、四类指标历史(CPU / 内存 / 磁盘 / 网络)、四合一聚合历史 metrics,以及 WebRTC 延迟探针的信令中转。
鉴权基调:本章全部 8 个接口都需要登录,通过 Authorization: Bearer <token> 请求头携带 JWT(仅支持请求头方式,不接受 URL 参数传递),Bearer 令牌的获取与使用方式详见认证指南。所有路由经过全站 WAF 防护。缓存差异:节点列表与节点详情为短缓存(ShortCache,响应携带 Cache-Control: public, max-age=60, s-maxage=60),四类历史与 metrics 一律禁止缓存(NoCache,响应携带 Cache-Control: no-store, no-cache, must-revalidate, private),探针中转为 POST 信令、未挂任何缓存中间件。所有接口均返回统一包络 {"code": 0, "message": "...", "data": ...},错误码与 HTTP 状态码对齐;与具体接口无关的通用约定见通用约定。
节点列表与详情
GET /api/nodes
返回所有对用户可见的节点,按地区(region)分组,并附带一份面板全局统计(在线节点数、在线用户数、今日流量等)。
鉴权:JWT Bearer · 限流:无专用限流(WAF 全局策略照常生效) · 缓存:ShortCache(60 秒)
请求示例
curl https://api.hyperfrp.com/api/nodes \
-H "Authorization: Bearer <token>"const res = await fetch('https://api.hyperfrp.com/api/nodes', {
headers: {
'Authorization': 'Bearer <token>',
},
});
const data = await res.json();
console.log(data);import requests
res = requests.get(
'https://api.hyperfrp.com/api/nodes',
headers={'Authorization': 'Bearer <token>'},
)
data = res.json()
print(data)package main
import (
"fmt"
"net/http"
)
func main() {
req, _ := http.NewRequest("GET", "https://api.hyperfrp.com/api/nodes", nil)
req.Header.Set("Authorization", "Bearer <token>")
res, err := http.DefaultClient.Do(req)
if err != nil {
panic(err)
}
defer res.Body.Close()
var result map[string]any
json.NewDecoder(res.Body).Decode(&result)
fmt.Println(result)
}响应字段
| 字段 | 类型 | 说明 |
|---|---|---|
nodes | object | 按地区分组的节点集合。键为地区名(管理员配置的 region 字符串,如 华南);region 为空串的节点归入 Overseas 键;值为该地区的 NodeResponse 数组 |
nodes.*[n].id | number | 节点 ID |
nodes.*[n].name | string | 节点名称(唯一) |
nodes.*[n].host | string | 节点主机地址(域名或 IP) |
nodes.*[n].address | string | 连接地址,当前与 host 同值(冗余字段) |
nodes.*[n].port | number | FRPS 主连接端口 |
nodes.*[n].location | string | 地理位置描述(如 中国 广东 广州) |
nodes.*[n].description | string | 节点描述 |
nodes.*[n].status | string | 节点状态,枚举:online / api_only / unreachable / frps_stopped / offline / maintenance |
nodes.*[n].clientsOnline | number | 该节点当前在线客户端(FRPC)数 |
nodes.*[n].proxiesCount | number | 该节点上的隧道总数 |
nodes.*[n].lastHeartbeat | string | null | 最近一次心跳时间(RFC3339);从未上报时为 null |
nodes.*[n].cpuUsage | number | CPU 占用率(百分比数字,如 23.4 表示 23.4%) |
nodes.*[n].memoryUsage | number | 内存占用率(百分比数字) |
nodes.*[n].diskUsage | number | 磁盘占用率(百分比数字) |
nodes.*[n].networkSpeedIn | number | 实时入站速度(字节/秒) |
nodes.*[n].networkSpeedOut | number | 实时出站速度(字节/秒) |
nodes.*[n].totalTrafficInBytes | number | 节点累计入站流量(字节) |
nodes.*[n].totalTrafficOutBytes | number | 节点累计出站流量(字节) |
nodes.*[n].region | string | 地区名,空串时前端应按 Overseas 处理 |
nodes.*[n].allowedProtocols | string[] | 允许的隧道协议类型(如 tcp / udp / http / https);无限制时为空数组 [],不会是 null |
nodes.*[n].bandwidth | string | 带宽描述文本,由数值 + 单位拼接(如 100Mbps、1Gbps) |
nodes.*[n].load | number | 综合负载估算值(0 左右的小数),由隧道估算负载与系统负载加权合成,见注意事项 |
nodes.*[n].loadAverage | string | 系统负载原文(如 0.52 0.48 0.45) |
nodes.*[n].systemLoad | string | 系统负载 JSON 文本(如 {"load1":0.52,"load5":0.48,"load15":0.45}) |
nodes.*[n].cpuInfo | string | CPU 型号描述 |
nodes.*[n].cpuTemperature | number | CPU 温度(摄氏度,未上报为 0) |
nodes.*[n].quicEnabled | boolean | 是否启用 QUIC 传输 |
nodes.*[n].isGroupExclusive | boolean | 是否为用户组专属节点 |
nodes.*[n].exclusiveTagText | string | 专属节点角标文案 |
nodes.*[n].exclusiveTagColor | string | 专属节点角标颜色 |
nodes.*[n].exclusiveTagIcon | string | 专属节点角标图标 |
nodes.*[n].speedTestPort | number | 测速服务端口(0 表示未开启) |
nodes.*[n].probeEnabled | boolean | 是否开启 WebRTC 延迟探针 |
nodes.*[n].probePort | number | 延迟探针端口(TCP 信令与 UDP 媒体共用,0 表示未配置) |
stats.onlineNodes | number | 在线节点数(可见节点中状态为 online / api_only / unreachable 三种均计入) |
stats.totalNodes | number | 可见节点总数 |
stats.onlineUsers | number | 在线用户数:取「各可见节点在线客户端数之和」与「拥有运行中隧道的去重用户数」二者的较大值 |
stats.onlineTunnels | number | 在线隧道数(状态为 running 的隧道总数) |
stats.todayUpload | number | 今日上传流量(字节,自服务器本地时区当日零点起累计) |
stats.todayDownload | number | 今日下载流量(字节,统计口径同上) |
stats.lastUpdated | string | 统计生成时间(RFC3339,服务器本地时区) |
响应示例
{
"code": 0,
"message": "成功",
"data": {
"nodes": {
"华南": [
{
"id": 2,
"name": "华南-广东-01",
"host": "gd-01.hyperfrp.com",
"address": "gd-01.hyperfrp.com",
"port": 7000,
"location": "中国 广东 广州",
"description": "广东电信 BGP 中转节点",
"status": "online",
"clientsOnline": 12,
"proxiesCount": 34,
"lastHeartbeat": "2026-09-19T22:58:31+08:00",
"cpuUsage": 23.4,
"memoryUsage": 61.8,
"diskUsage": 42.1,
"networkSpeedIn": 1048576,
"networkSpeedOut": 2097152,
"totalTrafficInBytes": 182536110080,
"totalTrafficOutBytes": 365072220160,
"region": "华南",
"allowedProtocols": ["tcp", "udp", "http", "https"],
"bandwidth": "100Mbps",
"load": 0.49,
"loadAverage": "0.52 0.48 0.45",
"systemLoad": "{\"load1\":0.52,\"load5\":0.48,\"load15\":0.45}",
"cpuInfo": "Intel Xeon Platinum 8269CY",
"cpuTemperature": 45.5,
"quicEnabled": true,
"isGroupExclusive": false,
"exclusiveTagText": "",
"exclusiveTagColor": "#ff69b4",
"exclusiveTagIcon": "",
"speedTestPort": 27000,
"probeEnabled": true,
"probePort": 27100
}
],
"Overseas": [
{
"id": 1,
"name": "HK-01",
"host": "hk-01.hyperfrp.com",
"address": "hk-01.hyperfrp.com",
"port": 7000,
"location": "中国 香港",
"description": "",
"status": "online",
"clientsOnline": 5,
"proxiesCount": 18,
"lastHeartbeat": "2026-09-19T22:58:02+08:00",
"cpuUsage": 11.2,
"memoryUsage": 43.6,
"diskUsage": 28.9,
"networkSpeedIn": 524288,
"networkSpeedOut": 262144,
"totalTrafficInBytes": 107374182400,
"totalTrafficOutBytes": 214748364800,
"region": "",
"allowedProtocols": [],
"bandwidth": "30Mbps",
"load": 0.21,
"loadAverage": "0.10 0.12 0.11",
"systemLoad": "",
"cpuInfo": "AMD EPYC 7K62",
"cpuTemperature": 0,
"quicEnabled": false,
"isGroupExclusive": false,
"exclusiveTagText": "",
"exclusiveTagColor": "#ff69b4",
"exclusiveTagIcon": "",
"speedTestPort": 0,
"probeEnabled": false,
"probePort": 0
}
]
},
"stats": {
"onlineNodes": 2,
"totalNodes": 2,
"onlineUsers": 15,
"onlineTunnels": 52,
"todayUpload": 5368709120,
"todayDownload": 10737418240,
"lastUpdated": "2026-09-19T23:01:44+08:00"
}
}
}错误场景
| 场景 | HTTP | 说明 |
|---|---|---|
| JWT 缺失 / 无效 / 会话失效 | 401 | 由鉴权中间件统一拦截,见认证指南 |
| 节点列表查询失败(数据库异常) | 500 | 获取节点列表失败 |
注意事项
- 仅返回
is_visible = true的节点,按节点名称升序排列;隐藏节点对本接口完全不可见。 load为服务端合成的估算负载:隧道估算负载 × 0.8 + 系统负载 × 0.2,其中隧道估算负载 =(proxiesCount × 2) / 带宽Mbps(Gbps单位会先换算为 Mbps),系统负载取loadAverage的首个数值;两者皆缺失时为 0。stats.todayUpload/stats.todayDownload是「流量历史记录」按当日零点汇总的字节数,不是实时速率;实时速率见各节点的networkSpeedIn/networkSpeedOut。- 响应缓存 60 秒(
max-age=60),刚刚上线的节点或状态跃迁最多延迟 1 分钟可见。
GET /api/nodes/:id
返回单个节点的完整配置与运行状态,即原始 Node 模型的 JSON 序列化结果(含 FRPS 配置参数)。
鉴权:JWT Bearer · 限流:无专用限流(WAF 全局策略照常生效) · 缓存:ShortCache(60 秒)
请求示例
curl https://api.hyperfrp.com/api/nodes/2 \
-H "Authorization: Bearer <token>"const res = await fetch('https://api.hyperfrp.com/api/nodes/2', {
headers: {
'Authorization': 'Bearer <token>',
},
});
const data = await res.json();
console.log(data);import requests
res = requests.get(
'https://api.hyperfrp.com/api/nodes/2',
headers={'Authorization': 'Bearer <token>'},
)
data = res.json()
print(data)package main
import (
"fmt"
"net/http"
)
func main() {
req, _ := http.NewRequest("GET", "https://api.hyperfrp.com/api/nodes/2", nil)
req.Header.Set("Authorization", "Bearer <token>")
res, err := http.DefaultClient.Do(req)
if err != nil {
panic(err)
}
defer res.Body.Close()
var result map[string]any
json.NewDecoder(res.Body).Decode(&result)
fmt.Println(result)
}响应字段
data 为原始 Node 模型。凭证类字段(token、hyperzonefrpApiToken、hyperzonefrpNodeToken、dashboardUser、dashboardPassword)在模型上标记为 json:"-",响应中不会出现。另有若干非数据库字段(memoryTotal、memoryUsed、diskTotal、diskUsed、logPath、frpsPath、configPath)本接口不做填充,恒为 0 或空串。
识别与展示
| 字段 | 类型 | 说明 |
|---|---|---|
id | number | 节点 ID |
name | string | 节点名称(唯一) |
host | string | 节点主机地址 |
domain | string | 节点域名(可为空串) |
location | string | 地理位置 |
description | string | 节点描述 |
region | string | 地区名,默认 Overseas |
status | string | 节点状态,枚举:online / api_only / unreachable / frps_stopped / offline / maintenance |
isVisible | boolean | 是否对用户可见(不可见节点无法通过本接口查询到) |
lastHeartbeat | string | null | 最近心跳时间(RFC3339) |
onlineSince | string | null | 本次上线起始时间(RFC3339) |
bootTime | number | null | 节点系统启动时间戳(秒) |
createdAt | string | 创建时间(RFC3339) |
updatedAt | string | 更新时间(RFC3339) |
接入与端口
| 字段 | 类型 | 说明 |
|---|---|---|
port | number | FRPS 主连接端口 |
bindAddress | string | 监听地址,默认 0.0.0.0 |
kcpBindPort | number | KCP 传输端口(0 = 未启用) |
quicEnabled | boolean | 是否启用 QUIC |
quicBindPort | number | QUIC 端口 |
vhostHTTPPort | number | HTTP 虚拟主机端口 |
vhostHTTPSPort | number | HTTPS 虚拟主机端口 |
dashboardPort | number | FRPS Dashboard 端口 |
webServerAddr | string | Dashboard 监听地址,默认 127.0.0.1 |
tcpMux | boolean | 是否启用 TCP 复用 |
tcpMuxHTTPConnectPort | number | TCP 复用 HTTP CONNECT 端口 |
tcpKeepAlive | number | TCP Keep-Alive 探测间隔(秒) |
heartbeatTimeout | number | 心跳超时(秒),默认 90 |
maxPoolCount | number | 客户端连接池上限,默认 5 |
tlsForce | boolean | 是否强制 TLS |
speedTestPort | number | 测速服务端口(0 = 未开启) |
probeEnabled | boolean | 是否开启 WebRTC 延迟探针 |
probePort | number | 探针端口(TCP 信令与 UDP 媒体共用) |
资源与监控
| 字段 | 类型 | 说明 |
|---|---|---|
cpuInfo | string | CPU 型号 |
cpuTemperature | number | CPU 温度(摄氏度) |
cpuUsage | number | CPU 占用率(百分比数字) |
loadAverage | string | 系统负载原文 |
systemLoad | string | 系统负载 JSON 文本 |
memoryUsage | number | 内存占用率(百分比数字) |
memoryTotal | number | 内存总量(字节);本接口恒为 0(非库字段,未填充) |
memoryUsed | number | 已用内存(字节);本接口恒为 0 |
diskUsage | number | 磁盘占用率(百分比数字) |
diskTotal | number | 磁盘总量(字节);本接口恒为 0 |
diskUsed | number | 已用磁盘(字节);本接口恒为 0 |
networkSpeedIn | number | 实时入站速度(字节/秒) |
networkSpeedOut | number | 实时出站速度(字节/秒) |
clientsOnline | number | 在线客户端数 |
proxiesCount | number | 隧道总数 |
流量与限额
| 字段 | 类型 | 说明 |
|---|---|---|
totalTrafficInBytes | number | 累计入站流量(字节) |
totalTrafficOutBytes | number | 累计出站流量(字节) |
frpsReportedInBytes | number | FRPS 最近一次上报的累计入站字节数(用于增量对账) |
frpsReportedOutBytes | number | FRPS 最近一次上报的累计出站字节数 |
networkPacketsSent | number | 网络发包计数(当前上报恒为 0) |
networkPacketsRecv | number | 网络收包计数(当前上报恒为 0) |
maxProxies | number | 单用户隧道数上限,默认 10 |
bandwidthLimitKbps | number | 单隧道带宽限制(Kbps),默认 10240 |
bandwidthValue | number | 节点带宽数值,默认 100 |
bandwidthUnit | string | 带宽单位,枚举:Mbps / Gbps |
准入控制
| 字段 | 类型 | 说明 |
|---|---|---|
allowedGroupIds | number[] | 允许访问的用户组 ID 列表;空数组表示不限 |
isGroupExclusive | boolean | 是否为用户组专属节点 |
permissionMessage | string | 无权限时展示的提示文案 |
exclusiveTagText | string | 专属角标文案 |
exclusiveTagColor | string | 专属角标颜色,默认 #ff69b4 |
exclusiveTagIcon | string | 专属角标图标 |
allowedPortsStart | number | 单段端口范围起点(0 = 不限) |
allowedPortsEnd | number | 单段端口范围终点 |
allowPortRanges | object[] | 多段端口范围数组,元素为 {"start": number, "end": number};非空时优先生效 |
allowedProtocols | string[] | 允许的隧道协议类型;空数组表示按默认策略 |
HyperzoneFRP 联动
| 字段 | 类型 | 说明 |
|---|---|---|
hyperzonefrpApiURL | string | 节点回连面板的 API 基址 |
hyperzonefrpHeartbeatInterval | number | 节点心跳上报间隔(秒),默认 30 |
hyperzonefrpResourceInterval | number | 节点资源上报间隔(秒),默认 15 |
hyperzonefrpTrafficFlushInterval | number | 节点流量冲账间隔(秒),默认 10 |
hyperzonefrpTunnelCheckInterval | number | 节点隧道巡检间隔(秒),默认 30 |
trafficQueuePath | string | 流量 WAL 路径(空串表示使用面板安全默认) |
trafficQueueHighWatermark | number | 流量队列高水位(条数),默认 10000 |
upgradeEnabled | boolean | 自动升级总开关,默认 false |
panelFailureMode | string | 面板不可用降级策略,枚举:fail-closed / fail-open |
upgradeServiceName | string | 升级器服务名 |
upgradeInstallDir | string | 升级器安装目录 |
upgradeSelfCheckWindow | number | 升级自检窗口(秒),默认 60 |
日志与其他
| 字段 | 类型 | 说明 |
|---|---|---|
logLevel | string | FRPS 日志级别,默认 info |
logMaxDays | number | FRPS 日志保留天数,默认 7 |
disableLogColor | boolean | 是否禁用日志颜色 |
logPath | string | 日志路径;本接口恒为空串(非库字段,未填充) |
frpsPath | string | FRPS 程序路径;本接口恒为空串 |
configPath | string | FRPS 配置路径;本接口恒为空串 |
响应示例
{
"code": 0,
"message": "成功",
"data": {
"id": 2,
"name": "华南-广东-01",
"host": "gd-01.hyperfrp.com",
"domain": "gd-01.hyperfrp.com",
"port": 7000,
"bindAddress": "0.0.0.0",
"kcpBindPort": 0,
"quicEnabled": true,
"quicBindPort": 7001,
"vhostHTTPPort": 8080,
"vhostHTTPSPort": 8443,
"speedTestPort": 27000,
"probeEnabled": true,
"probePort": 27100,
"allowedGroupIds": [1, 3],
"isGroupExclusive": false,
"permissionMessage": "",
"exclusiveTagText": "",
"exclusiveTagColor": "#ff69b4",
"exclusiveTagIcon": "",
"allowedPortsStart": 10000,
"allowedPortsEnd": 65535,
"allowPortRanges": [],
"location": "中国 广东 广州",
"description": "广东电信 BGP 中转节点",
"status": "online",
"cpuInfo": "Intel Xeon Platinum 8269CY",
"cpuTemperature": 45.5,
"isVisible": true,
"maxProxies": 20,
"bandwidthLimitKbps": 10240,
"totalTrafficInBytes": 182536110080,
"totalTrafficOutBytes": 365072220160,
"networkPacketsSent": 0,
"networkPacketsRecv": 0,
"clientsOnline": 12,
"proxiesCount": 34,
"lastHeartbeat": "2026-09-19T22:58:31+08:00",
"onlineSince": "2026-09-18T08:12:00+08:00",
"bootTime": 1758200000,
"cpuUsage": 23.4,
"loadAverage": "0.52 0.48 0.45",
"systemLoad": "{\"load1\":0.52,\"load5\":0.48,\"load15\":0.45}",
"memoryUsage": 61.8,
"memoryTotal": 0,
"memoryUsed": 0,
"diskUsage": 42.1,
"diskTotal": 0,
"diskUsed": 0,
"networkSpeedIn": 1048576,
"networkSpeedOut": 2097152,
"frpsReportedInBytes": 182536110080,
"frpsReportedOutBytes": 365072220160,
"logPath": "",
"frpsPath": "",
"configPath": "",
"dashboardPort": 7500,
"webServerAddr": "127.0.0.1",
"tcpMux": true,
"tcpMuxHTTPConnectPort": 0,
"tcpKeepAlive": 7200,
"heartbeatTimeout": 90,
"maxPoolCount": 5,
"tlsForce": false,
"logLevel": "info",
"logMaxDays": 7,
"disableLogColor": false,
"region": "华南",
"allowedProtocols": ["tcp", "udp", "http", "https"],
"bandwidthValue": 100,
"bandwidthUnit": "Mbps",
"hyperzonefrpApiURL": "https://api.hyperfrp.com/api",
"hyperzonefrpHeartbeatInterval": 30,
"hyperzonefrpResourceInterval": 15,
"hyperzonefrpTrafficFlushInterval": 10,
"hyperzonefrpTunnelCheckInterval": 30,
"trafficQueuePath": "",
"trafficQueueHighWatermark": 10000,
"upgradeEnabled": false,
"panelFailureMode": "fail-closed",
"upgradeServiceName": "",
"upgradeInstallDir": "",
"upgradeSelfCheckWindow": 60,
"createdAt": "2026-06-01T10:00:00+08:00",
"updatedAt": "2026-09-19T22:58:31+08:00"
}
}错误场景
| 场景 | HTTP | 说明 |
|---|---|---|
| JWT 缺失 / 无效 / 会话失效 | 401 | 由鉴权中间件统一拦截 |
节点不存在,或存在但对用户不可见(is_visible = false) | 404 | 节点未找到 |
历史监控
四个单指标历史端点与聚合端点 metrics 同构:数据来自节点资源上报写入的 node_metric_histories 表,按时间桶聚合(桶内取均值)。时间范围二选一:提供 startDate + endDate 时按日期区间查询;否则按 interval 决定(hour = 近 24 小时按小时聚合;缺省或其他任意值 = 近 30 天按天聚合)。
GET /api/nodes/:id/cpu-history
返回节点 CPU 占用率的历史曲线,元素为 {time, value}。
鉴权:JWT Bearer · 限流:无专用限流(WAF 全局策略照常生效) · 缓存:NoCache
查询参数
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
interval | string | 否 | hour:近 24 小时、按小时聚合;缺省或其他任意值:近 30 天、按天聚合 |
startDate | string | 否 | 起始日期,格式 YYYY-MM-DD;与 endDate 同时提供时生效,并优先于 interval |
endDate | string | 否 | 结束日期,格式 YYYY-MM-DD;聚合时结束日期向后扩展 24 小时(即含 endDate 当天全天) |
请求示例
curl "https://api.hyperfrp.com/api/nodes/2/cpu-history?interval=hour" \
-H "Authorization: Bearer <token>"
# 按日期区间查询
curl "https://api.hyperfrp.com/api/nodes/2/cpu-history?startDate=2026-09-01&endDate=2026-09-19" \
-H "Authorization: Bearer <token>"// 按日期区间查询:GET /api/nodes/2/cpu-history?startDate=2026-09-01&endDate=2026-09-19
const res = await fetch('https://api.hyperfrp.com/api/nodes/2/cpu-history?interval=hour', {
headers: {
'Authorization': 'Bearer <token>',
},
});
const data = await res.json();
console.log(data);import requests
# 按日期区间查询:GET /api/nodes/2/cpu-history?startDate=2026-09-01&endDate=2026-09-19
res = requests.get(
'https://api.hyperfrp.com/api/nodes/2/cpu-history?interval=hour',
headers={'Authorization': 'Bearer <token>'},
)
data = res.json()
print(data)// 按日期区间查询:GET /api/nodes/2/cpu-history?startDate=2026-09-01&endDate=2026-09-19
package main
import (
"fmt"
"net/http"
)
func main() {
req, _ := http.NewRequest("GET", "https://api.hyperfrp.com/api/nodes/2/cpu-history?interval=hour", nil)
req.Header.Set("Authorization", "Bearer <token>")
res, err := http.DefaultClient.Do(req)
if err != nil {
panic(err)
}
defer res.Body.Close()
var result map[string]any
json.NewDecoder(res.Body).Decode(&result)
fmt.Println(result)
}响应字段
| 字段 | 类型 | 说明 |
|---|---|---|
[n].time | string | 时间桶标签:interval=hour 时为 YYYY-MM-DD HH:00;按天聚合时为 YYYY-MM-DD |
[n].value | number | 该时间桶内 CPU 占用率均值(百分比数字,0~100) |
响应示例
{
"code": 0,
"message": "成功",
"data": [
{ "time": "2026-09-19 14:00", "value": 23.4 },
{ "time": "2026-09-19 15:00", "value": 31.2 },
{ "time": "2026-09-19 16:00", "value": 18.7 }
]
}错误场景
| 场景 | HTTP | 说明 |
|---|---|---|
| JWT 缺失 / 无效 / 会话失效 | 401 | 由鉴权中间件统一拦截 |
| 节点不存在 / 不可见 / 无历史数据 | 200 | 返回空数组 [],不报 404 |
注意事项
startDate/endDate仅在两者同时提供且格式合法时生效,否则回落到interval逻辑。- 返回数组的顺序不保证按时间升序(聚合按分组结果遍历),绘制曲线前请先按
time排序。
GET /api/nodes/:id/memory-history
返回节点内存占用率的历史曲线,结构与 cpu-history 完全一致。
鉴权:JWT Bearer · 限流:无专用限流(WAF 全局策略照常生效) · 缓存:NoCache
查询参数
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
interval | string | 否 | hour:近 24 小时、按小时聚合;缺省或其他任意值:近 30 天、按天聚合 |
startDate | string | 否 | 起始日期,格式 YYYY-MM-DD;与 endDate 同时提供时生效并优先于 interval |
endDate | string | 否 | 结束日期,格式 YYYY-MM-DD,含当天全天 |
请求示例
curl "https://api.hyperfrp.com/api/nodes/2/memory-history?interval=hour" \
-H "Authorization: Bearer <token>"const res = await fetch('https://api.hyperfrp.com/api/nodes/2/memory-history?interval=hour', {
headers: {
'Authorization': 'Bearer <token>',
},
});
const data = await res.json();
console.log(data);import requests
res = requests.get(
'https://api.hyperfrp.com/api/nodes/2/memory-history?interval=hour',
headers={'Authorization': 'Bearer <token>'},
)
data = res.json()
print(data)package main
import (
"fmt"
"net/http"
)
func main() {
req, _ := http.NewRequest("GET", "https://api.hyperfrp.com/api/nodes/2/memory-history?interval=hour", nil)
req.Header.Set("Authorization", "Bearer <token>")
res, err := http.DefaultClient.Do(req)
if err != nil {
panic(err)
}
defer res.Body.Close()
var result map[string]any
json.NewDecoder(res.Body).Decode(&result)
fmt.Println(result)
}响应字段
| 字段 | 类型 | 说明 |
|---|---|---|
[n].time | string | 时间桶标签,格式同 cpu-history |
[n].value | number | 该时间桶内内存占用率均值(百分比数字,0~100) |
响应示例
{
"code": 0,
"message": "成功",
"data": [
{ "time": "2026-09-19 14:00", "value": 61.8 },
{ "time": "2026-09-19 15:00", "value": 63.1 }
]
}错误场景
| 场景 | HTTP | 说明 |
|---|---|---|
| JWT 缺失 / 无效 / 会话失效 | 401 | 由鉴权中间件统一拦截 |
| 节点不存在 / 不可见 / 无历史数据 | 200 | 返回空数组 [],不报 404 |
注意事项
- 返回数组顺序不保证按时间升序,绘制前请按
time排序。
GET /api/nodes/:id/disk-history
返回节点磁盘占用率的历史曲线,结构与 cpu-history 完全一致。
鉴权:JWT Bearer · 限流:无专用限流(WAF 全局策略照常生效) · 缓存:NoCache
查询参数
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
interval | string | 否 | hour:近 24 小时、按小时聚合;缺省或其他任意值:近 30 天、按天聚合 |
startDate | string | 否 | 起始日期,格式 YYYY-MM-DD;与 endDate 同时提供时生效并优先于 interval |
endDate | string | 否 | 结束日期,格式 YYYY-MM-DD,含当天全天 |
请求示例
curl "https://api.hyperfrp.com/api/nodes/2/disk-history?startDate=2026-09-01&endDate=2026-09-19" \
-H "Authorization: Bearer <token>"const res = await fetch('https://api.hyperfrp.com/api/nodes/2/disk-history?startDate=2026-09-01&endDate=2026-09-19', {
headers: {
'Authorization': 'Bearer <token>',
},
});
const data = await res.json();
console.log(data);import requests
res = requests.get(
'https://api.hyperfrp.com/api/nodes/2/disk-history?startDate=2026-09-01&endDate=2026-09-19',
headers={'Authorization': 'Bearer <token>'},
)
data = res.json()
print(data)package main
import (
"fmt"
"net/http"
)
func main() {
req, _ := http.NewRequest("GET", "https://api.hyperfrp.com/api/nodes/2/disk-history?startDate=2026-09-01&endDate=2026-09-19", nil)
req.Header.Set("Authorization", "Bearer <token>")
res, err := http.DefaultClient.Do(req)
if err != nil {
panic(err)
}
defer res.Body.Close()
var result map[string]any
json.NewDecoder(res.Body).Decode(&result)
fmt.Println(result)
}响应字段
| 字段 | 类型 | 说明 |
|---|---|---|
[n].time | string | 时间桶标签,格式同 cpu-history |
[n].value | number | 该时间桶内磁盘占用率均值(百分比数字,0~100) |
响应示例
{
"code": 0,
"message": "成功",
"data": [
{ "time": "2026-09-18", "value": 41.9 },
{ "time": "2026-09-19", "value": 42.1 }
]
}错误场景
| 场景 | HTTP | 说明 |
|---|---|---|
| JWT 缺失 / 无效 / 会话失效 | 401 | 由鉴权中间件统一拦截 |
| 节点不存在 / 不可见 / 无历史数据 | 200 | 返回空数组 [],不报 404 |
注意事项
- 返回数组顺序不保证按时间升序,绘制前请按
time排序。
GET /api/nodes/:id/network-history
返回节点网络速率的历史曲线。每个时间桶产出两条记录(入站 / 出站各一条),通过 type 字段区分。
鉴权:JWT Bearer · 限流:无专用限流(WAF 全局策略照常生效) · 缓存:NoCache
查询参数
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
interval | string | 否 | hour:近 24 小时、按小时聚合;缺省或其他任意值:近 30 天、按天聚合 |
startDate | string | 否 | 起始日期,格式 YYYY-MM-DD;与 endDate 同时提供时生效并优先于 interval |
endDate | string | 否 | 结束日期,格式 YYYY-MM-DD,含当天全天 |
请求示例
curl "https://api.hyperfrp.com/api/nodes/2/network-history?interval=hour" \
-H "Authorization: Bearer <token>"const res = await fetch('https://api.hyperfrp.com/api/nodes/2/network-history?interval=hour', {
headers: {
'Authorization': 'Bearer <token>',
},
});
const data = await res.json();
console.log(data);import requests
res = requests.get(
'https://api.hyperfrp.com/api/nodes/2/network-history?interval=hour',
headers={'Authorization': 'Bearer <token>'},
)
data = res.json()
print(data)package main
import (
"fmt"
"net/http"
)
func main() {
req, _ := http.NewRequest("GET", "https://api.hyperfrp.com/api/nodes/2/network-history?interval=hour", nil)
req.Header.Set("Authorization", "Bearer <token>")
res, err := http.DefaultClient.Do(req)
if err != nil {
panic(err)
}
defer res.Body.Close()
var result map[string]any
json.NewDecoder(res.Body).Decode(&result)
fmt.Println(result)
}响应字段
| 字段 | 类型 | 说明 |
|---|---|---|
[n].time | string | 时间桶标签,格式同 cpu-history |
[n].value | number | 该时间桶内网络速率均值(字节/秒) |
[n].type | string | 曲线类别,固定两个值:入站速度(对应 networkSpeedIn 均值)与 出站速度(对应 networkSpeedOut 均值) |
响应示例
{
"code": 0,
"message": "成功",
"data": [
{ "time": "2026-09-19 14:00", "value": 1048576, "type": "入站速度" },
{ "time": "2026-09-19 14:00", "value": 2097152, "type": "出站速度" },
{ "time": "2026-09-19 15:00", "value": 786432, "type": "入站速度" },
{ "time": "2026-09-19 15:00", "value": 1572864, "type": "出站速度" }
]
}错误场景
| 场景 | HTTP | 说明 |
|---|---|---|
| JWT 缺失 / 无效 / 会话失效 | 401 | 由鉴权中间件统一拦截 |
| 节点不存在 / 不可见 / 无历史数据 | 200 | 返回空数组 [],不报 404 |
注意事项
- 每个时间桶固定产出
入站速度+出站速度两条记录,数组长度约为时间桶数的 2 倍。 type取值为中文字面量入站速度/出站速度,前端按该字符串分组曲线。- 返回数组顺序不保证按时间升序,绘制前请按
time排序。
GET /api/nodes/:id/metrics
一次请求返回四类指标的聚合历史(CPU / 内存 / 磁盘 / 网络双向),字段名为 snake_case。本端点不支持 startDate / endDate,仅按 interval 取时间窗。
鉴权:JWT Bearer · 限流:无专用限流(WAF 全局策略照常生效) · 缓存:NoCache
查询参数
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
interval | string | 否 | hour:近 24 小时、按小时聚合;缺省(服务端置为 day)或其他值:近 30 天、按天聚合 |
请求示例
curl "https://api.hyperfrp.com/api/nodes/2/metrics?interval=hour" \
-H "Authorization: Bearer <token>"const res = await fetch('https://api.hyperfrp.com/api/nodes/2/metrics?interval=hour', {
headers: {
'Authorization': 'Bearer <token>',
},
});
const data = await res.json();
console.log(data);import requests
res = requests.get(
'https://api.hyperfrp.com/api/nodes/2/metrics?interval=hour',
headers={'Authorization': 'Bearer <token>'},
)
data = res.json()
print(data)package main
import (
"fmt"
"net/http"
)
func main() {
req, _ := http.NewRequest("GET", "https://api.hyperfrp.com/api/nodes/2/metrics?interval=hour", nil)
req.Header.Set("Authorization", "Bearer <token>")
res, err := http.DefaultClient.Do(req)
if err != nil {
panic(err)
}
defer res.Body.Close()
var result map[string]any
json.NewDecoder(res.Body).Decode(&result)
fmt.Println(result)
}响应字段
| 字段 | 类型 | 说明 |
|---|---|---|
[n].time | string | 时间桶标签:interval=hour 时为 YYYY-MM-DD HH:00;按天聚合时为 YYYY-MM-DD |
[n].cpu_usage | number | 该时间桶内 CPU 占用率均值(百分比数字,0~100) |
[n].memory_usage | number | 该时间桶内内存占用率均值(百分比数字,0~100) |
[n].disk_usage | number | 该时间桶内磁盘占用率均值(百分比数字,0~100) |
[n].network_in | number | 该时间桶内入站速率均值(字节/秒) |
[n].network_out | number | 该时间桶内出站速率均值(字节/秒) |
响应示例
{
"code": 0,
"message": "成功",
"data": [
{
"time": "2026-09-19 14:00",
"cpu_usage": 23.4,
"memory_usage": 61.8,
"disk_usage": 42.1,
"network_in": 1048576,
"network_out": 2097152
},
{
"time": "2026-09-19 15:00",
"cpu_usage": 31.2,
"memory_usage": 63.1,
"disk_usage": 42.1,
"network_in": 786432,
"network_out": 1572864
}
]
}错误场景
| 场景 | HTTP | 说明 |
|---|---|---|
| JWT 缺失 / 无效 / 会话失效 | 401 | 由鉴权中间件统一拦截 |
| 节点不存在 / 不可见 / 无历史数据 | 200 | 返回空数组 [],不报 404 |
注意事项
- 字段名与其他历史端点不同:本端点为 snake_case(
cpu_usage等),其余端点为驼峰 / 小写(cpuUsage、value),对接时注意区分。 - 百分比字段是 0~100 的数字而非带
%的字符串;网络字段单位是字节/秒。 - 返回数组顺序不保证按时间升序,绘制前请按
time排序。
延迟探测
POST /api/nodes/:id/probe
WebRTC 延迟探针的信令中转:浏览器把本地 SDP offer 提交给面板(同源 HTTPS),面板用节点的联动密钥签发一次性 proof 后转发到节点探针端口,再把节点返回的 answer SDP 原样回传;浏览器据此与节点直连建立 DataChannel,测量真实 RTT。
鉴权:JWT Bearer · 限流:每用户令牌桶 20 次/分钟(容量 20、匀速补充,超出返回 429) · 缓存:无(路由未挂缓存中间件)
请求体
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
sdp | string | 是 | 浏览器本地 RTCPeerConnection 生成的 SDP offer 全文;非空且不超过 16384 字节(16 KiB) |
请求示例
curl -X POST https://api.hyperfrp.com/api/nodes/2/probe \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{"sdp":"v=0\r\no=- 4611731395439611998 2 IN IP4 127.0.0.1\r\ns=-\r\nt=0 0\r\na=group:BUNDLE 0\r\na=ice-ufrag:4ZcD\r\na=ice-pwd:2/1muCXRGhNyMM5uMpuP9YLHy\r\n..."}'const res = await fetch('https://api.hyperfrp.com/api/nodes/2/probe', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer <token>',
},
body: JSON.stringify({"sdp":"v=0\r\no=- 4611731395439611998 2 IN IP4 127.0.0.1\r\ns=-\r\nt=0 0\r\na=group:BUNDLE 0\r\na=ice-ufrag:4ZcD\r\na=ice-pwd:2/1muCXRGhNyMM5uMpuP9YLHy\r\n..."})
});
const data = await res.json();
console.log(data);import requests
res = requests.post(
'https://api.hyperfrp.com/api/nodes/2/probe',
headers={'Authorization': 'Bearer <token>'},
json={
'sdp': 'v=0
o=- 4611731395439611998 2 IN IP4 127.0.0.1
s=-
t=0 0
a=group:BUNDLE 0
a=ice-ufrag:4ZcD
a=ice-pwd:2/1muCXRGhNyMM5uMpuP9YLHy
...'
},
)
data = res.json()
print(data)package main
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
)
func main() {
var payload bytes.Buffer
json.NewEncoder(&payload).Encode(map[string]any{
"sdp": "v=0\r\no=- 4611731395439611998 2 IN IP4 127.0.0.1\r\ns=-\r\nt=0 0\r\na=group:BUNDLE 0\r\na=ice-ufrag:4ZcD\r\na=ice-pwd:2/1muCXRGhNyMM5uMpuP9YLHy\r\n...",
})
req, _ := http.NewRequest("POST", "https://api.hyperfrp.com/api/nodes/2/probe", &payload)
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Authorization", "Bearer <token>")
res, err := http.DefaultClient.Do(req)
if err != nil {
panic(err)
}
defer res.Body.Close()
var result map[string]any
json.NewDecoder(res.Body).Decode(&result)
fmt.Println(result)
}响应字段
| 字段 | 类型 | 说明 |
|---|---|---|
sdp | string | 节点返回的 SDP answer 全文,面板原样透传不做解析 |
响应示例
{
"code": 0,
"message": "成功",
"data": {
"sdp": "v=0\r\no=- 4611731409170018437 2 IN IP4 127.0.0.1\r\ns=-\r\nt=0 0\r\na=group:BUNDLE 0\r\na=ice-ufrag:8Km2\r\na=ice-pwd:xJ9pQ2wRvN5tYbCd3eFgHi\r\n..."
}
}错误场景
| 场景 | HTTP | 说明 |
|---|---|---|
| JWT 缺失 / 无效 / 会话失效 | 401 | 由鉴权中间件统一拦截 |
| 当前用户令牌桶耗尽(超过 20 次/分钟) | 429 | 延迟探测过于频繁,请稍候 |
| 节点不存在 / 不可见 | 404 | 节点未找到 |
节点未开启探测(probeEnabled = false 或 probePort <= 0) | 400 | 该节点未开启延迟探测 |
请求体缺失 / 非法 JSON / sdp 为空 | 400 | 缺少 sdp |
sdp 超过 16384 字节 | 400 | sdp 过大 |
| 面板无法连上节点探针端口(含 8 秒转发超时) | 400 | 节点探测端口不可达 |
| 节点拒绝本次探测 offer | 400 | 节点拒绝探测请求(<HTTP状态码>) |
| 节点探测侧自身限流(返回 429) | 429 | 节点探测繁忙,请稍后重试 |
| 节点应答解析失败或 answer 为空 | 400 | 节点返回的 answer 无效 |
| 节点缺少面板联动密钥,无法签发探测凭证 | 500 | 节点缺少面板联动密钥,无法签发探测凭证 |
| 服务端生成随机 nonce 失败 | 500 | 生成 nonce 失败 |
注意事项
- 为什么必须经面板同源中转:控制台页面运行在 HTTPS 下,节点探针端口是节点侧的裸 HTTP 服务,浏览器直接请求属于混合内容,会被整段拦截;同时浏览器也无法自行签发节点认得的凭证。面板作为受信中继,使用面板持有的节点联动密钥签发探测凭证,该密钥从不下发给浏览器;浏览器只提交 SDP,收到 answer 后与节点直连测真实 RTT。
- 一次性防伪:面板为每次转发签发一次性探测凭证发往节点,节点校验通过后仅应答一次,重放无效;凭证签发细节不对外公开。
- 限流先于节点校验执行:按用户维度计,刚触发限流时响应与节点侧限流同为 429,按 message 区分即可。
- 探测端点不可达、节点拒绝等转发失败统一以 400 返回(节点侧 429 除外),SDP 本身超限(16 KiB)也会被 400 拒绝;浏览器侧生成 offer 时无需控制候选数量,但过大仍会被拒。