GraphQL API
EnerOS 通过 eneros-graphql crate 提供 GraphQL 接口,端点为 POST /graphql。GraphQL 适合需要灵活字段选取、多类型聚合查询的场景,避免 REST 的 over-fetching / under-fetching 问题。EnerOS 的 GraphQL 实现基于 async-graphql,完全异步、零拷贝,与内核共享同一套系统调用。
端点信息
| 项目 | 取值 |
|---|---|
| URL | http://localhost:8080/graphql |
| 方法 | POST |
| Content-Type | application/json |
| 认证 | Bearer Token(Header)或 mTLS |
| 订阅端点 | ws://localhost:8080/graphql(WebSocket Subscriptions 协议) |
| Introspection | 开发环境开启,生产环境默认关闭 |
| 查询深度限制 | 7 层 |
| 复杂度上限 | 1000 |
| 查询超时 | 30 秒 |
| 单次别名限制 | 20 |
请求格式
GraphQL 请求体统一为 JSON,包含以下字段:
| 字段 | 类型 | 必填 | 默认值 | 说明 |
|---|---|---|---|---|
query | string | 是 | — | GraphQL 查询字符串 |
variables | object | 否 | null | 变量字典 |
operationName | string | 否 | — | 当 query 包含多个操作时指定 |
extensions | object | 否 | — | 扩展字段(如持久化查询) |
完整 Schema
标量类型
EnerOS 在标准标量(String/Int/Float/Boolean/ID)之外,定义以下自定义标量:
scalar DateTime # RFC 3339 时间字符串
scalar JSON # 任意 JSON 值
scalar Float64 # IEEE 754 double
scalar UInt64 # 64 位无符号整数
scalar Decimal # 高精度十进制(电气量计算)
枚举类型
enum NetworkStatus {
DRAFT
READY
LOCKED
ARCHIVED
}
enum AgentStatus {
IDLE
RUNNING
PAUSED
ERROR
STOPPED
}
enum AgentType {
DISPATCH
SELF_HEALING
TRADING
MAINTENANCE
PLANNING
}
enum BusType {
SLACK
PV
PQ
}
enum DeviceType {
PV
WIND
BESS
EV
LOAD
TRANSFORMER
BREAKER
}
enum PowerFlowMethod {
NEWTON_RAPHSON
FAST_DECOUPLED
DC
}
enum PowerFlowStatus {
QUEUED
RUNNING
CONVERGED
DIVERGED
TIMEOUT
CANCELLED
}
enum Aggregation {
NONE
AVG
SUM
MAX
MIN
COUNT
PERCENTILE_50
PERCENTILE_95
PERCENTILE_99
}
enum Severity {
INFO
WARN
ERROR
CRITICAL
}
enum AlarmStatus {
ACTIVE
ACKNOWLEDGED
CLEARED
}
enum CommandPriority {
LOW
NORMAL
HIGH
EMERGENCY
}
enum BranchStatus {
CLOSED
OPEN
MAINTENANCE
}
输入类型
input NetworkFilter {
status: NetworkStatus
nameContains: String
tags: [TagFilterInput!]
createdAfter: DateTime
createdBefore: DateTime
}
input TagFilterInput {
key: String!
value: String
}
input NetworkInput {
name: String!
description: String
baseKv: Float
baseMva: Float
buses: [BusInput!]!
branches: [BranchInput!]
generators: [GeneratorInput!]
loads: [LoadInput!]
tags: JSON
}
input BusInput {
id: Int!
type: BusType!
v: Float
theta: Float
area: Int
}
input BranchInput {
from: Int!
to: Int!
r: Float!
x: Float!
b: Float
status: BranchStatus
}
input GeneratorInput {
bus: Int!
mw: Float!
mvar: Float
pmin: Float
pmax: Float
qmin: Float
qmax: Float
}
input LoadInput {
bus: Int!
mw: Float!
mvar: Float
}
input AgentInput {
name: String!
type: AgentType!
networkId: ID
config: JSON
tools: [String!]
reasoningModel: String
maxActionsPerMinute: Int
autoStart: Boolean
}
input CommandInput {
command: String!
params: JSON
priority: CommandPriority
timeoutMs: Int
awaitResult: Boolean
}
input TimeSeriesQuery {
metric: String!
tags: [TagFilterInput!]
from: DateTime!
to: DateTime!
aggregation: Aggregation
interval: String
fill: String
limit: Int
}
对象类型
type Network {
id: ID!
name: String!
description: String
baseKv: Float!
baseMva: Float!
busCount: Int!
branchCount: Int!
generatorCount: Int!
loadCount: Int!
status: NetworkStatus!
tags: JSON
buses: [Bus!]!
branches: [Branch!]!
generators: [Generator!]!
loads: [Load!]!
createdAt: DateTime!
updatedAt: DateTime!
powerFlow(method: PowerFlowMethod): PowerFlowResult
agents: [Agent!]!
alarms(severity: Severity): [Alarm!]!
}
type Bus {
id: Int!
type: BusType!
voltage: Voltage!
area: Int
generators: [Generator!]!
loads: [Load!]!
}
type Voltage {
magnitude: Float!
angle: Float!
}
type Branch {
from: Int!
to: Int!
r: Float!
x: Float!
b: Float!
status: BranchStatus!
flow: BranchFlow
}
type BranchFlow {
pFrom: Float!
qFrom: Float!
pTo: Float!
qTo: Float!
loading: Float!
}
type Generator {
bus: Int!
mw: Float!
mvar: Float!
pmin: Float
pmax: Float
qmin: Float
qmax: Float
}
type Load {
bus: Int!
mw: Float!
mvar: Float!
}
type Agent {
id: ID!
name: String!
type: AgentType!
networkId: ID
network: Network
status: AgentStatus!
config: JSON
tools: [String!]!
reasoningModel: String!
maxActionsPerMinute: Int!
createdAt: DateTime!
startedAt: DateTime
lastActiveAt: DateTime
actions: [AgentAction!]!
}
type AgentAction {
id: ID!
agentId: ID!
command: String!
params: JSON
status: String!
result: JSON
executedAt: DateTime!
durationMs: Int!
}
type PowerFlowResult {
taskId: ID!
networkId: ID!
status: PowerFlowStatus!
method: PowerFlowMethod!
iterations: Int
durationMs: Int!
tolerance: Float!
buses: [BusResult!]!
branches: [BranchResult!]!
losses: Losses
startedAt: DateTime!
completedAt: DateTime
}
type BusResult {
id: Int!
v: Float!
theta: Float!
pGen: Float
qGen: Float
pLoad: Float
qLoad: Float
}
type BranchResult {
from: Int!
to: Int!
pFrom: Float!
qFrom: Float!
pTo: Float!
qTo: Float!
loading: Float!
}
type Losses {
pMw: Float!
qMvar: Float!
}
type Device {
id: ID!
name: String!
type: DeviceType!
status: String!
networkId: ID
bus: Int
capacityMw: Float
online: Boolean!
metadata: JSON
lastSeenAt: DateTime
}
type TimeSeriesPoint {
timestamp: DateTime!
value: Float!
tags: JSON
}
type Alarm {
id: ID!
severity: Severity!
status: AlarmStatus!
type: String!
message: String!
networkId: ID
source: AlarmSource!
details: JSON
raisedAt: DateTime!
acknowledgedAt: DateTime
acknowledgedBy: String
clearedAt: DateTime
}
type AlarmSource {
type: String!
id: ID!
}
type CommandResult {
commandId: ID!
agentId: ID!
command: String!
status: String!
result: JSON
durationMs: Int!
executedAt: DateTime!
}
Query 类型
type Query {
# 网络
networks(filter: NetworkFilter, page: Int, pageSize: Int): NetworkConnection!
network(id: ID!): Network
# Agent
agents(status: AgentStatus, type: AgentType, page: Int, pageSize: Int): AgentConnection!
agent(id: ID!): Agent
# 设备
devices(type: DeviceType, online: Boolean, page: Int, pageSize: Int): DeviceConnection!
device(id: ID!): Device
# 时序
timeseries(query: TimeSeriesQuery!): [TimeSeriesPoint!]!
timeseriesMulti(queries: [TimeSeriesQuery!]!): [[TimeSeriesPoint!]!]!
# 事件与告警
events(topic: String, from: DateTime, to: DateTime, page: Int, pageSize: Int): EventConnection!
alarms(severity: Severity, status: AlarmStatus, from: DateTime, to: DateTime): [Alarm!]!
alarm(id: ID!): Alarm
# 审计
audit(actor: String, action: String, from: DateTime, to: DateTime, page: Int, pageSize: Int): AuditConnection!
# 健康
health: HealthStatus!
}
type NetworkConnection {
items: [Network!]!
total: Int!
page: Int!
pageSize: Int!
}
type AgentConnection {
items: [Agent!]!
total: Int!
page: Int!
pageSize: Int!
}
type DeviceConnection {
items: [Device!]!
total: Int!
page: Int!
pageSize: Int!
}
type EventConnection {
items: [Event!]!
total: Int!
page: Int!
pageSize: Int!
}
type AuditConnection {
items: [AuditEntry!]!
total: Int!
page: Int!
pageSize: Int!
}
type Event {
id: ID!
topic: String!
kind: String!
payload: JSON!
timestamp: DateTime!
}
type AuditEntry {
id: ID!
timestamp: DateTime!
actorType: String!
actorId: String!
tenant: String!
action: String!
resourceType: String!
resourceId: ID!
requestId: ID!
ip: String
userAgent: String
result: String!
details: JSON
}
type HealthStatus {
status: String!
version: String!
uptimeSeconds: Int!
checks: JSON!
}
Mutation 类型
type Mutation {
# 网络
createNetwork(input: NetworkInput!): Network!
updateNetwork(id: ID!, input: NetworkInput!): Network!
deleteNetwork(id: ID!): Boolean!
validateNetwork(id: ID!): ValidationResult!
# 潮流
runPowerFlow(id: ID!, method: PowerFlowMethod, async: Boolean): PowerFlowResult!
cancelTask(taskId: ID!): Boolean!
# Agent
createAgent(input: AgentInput!): Agent!
updateAgent(id: ID!, input: AgentInput!): Agent!
deleteAgent(id: ID!): Boolean!
pauseAgent(id: ID!): Agent!
resumeAgent(id: ID!): Agent!
sendCommand(agentId: ID!, command: CommandInput!): CommandResult!
# 告警
acknowledgeAlarm(id: ID!, note: String): Alarm!
# 时序写入
writeTimeseries(points: [TimeSeriesPointInput!]!): WriteResult!
}
type ValidationResult {
valid: Boolean!
errors: [ValidationError!]
}
type ValidationError {
field: String!
message: String!
code: String!
}
input TimeSeriesPointInput {
metric: String!
timestamp: DateTime
value: Float!
tags: JSON
}
type WriteResult {
accepted: Int!
rejected: Int!
errors: [WriteError!]
}
type WriteError {
index: Int!
message: String!
}
Subscription 类型
type Subscription {
networkChanged(id: ID): NetworkEvent!
agentStatusChanged(id: ID): AgentStatusEvent!
alarmStream(severity: Severity): Alarm!
eventStream(topic: String): Event!
powerFlowProgress(taskId: ID!): PowerFlowProgress!
}
type NetworkEvent {
networkId: ID!
kind: String!
changes: JSON!
timestamp: DateTime!
}
type AgentStatusEvent {
agentId: ID!
oldStatus: AgentStatus!
newStatus: AgentStatus!
reason: String
timestamp: DateTime!
}
type PowerFlowProgress {
taskId: ID!
iteration: Int!
maxIterations: Int!
mismatch: Float!
timestamp: DateTime!
}
Query 示例
查询电网与潮流结果
query GetNetworkWithPowerFlow($id: ID!) {
network(id: $id) {
id
name
baseKv
status
buses {
id
type
voltage { magnitude angle }
}
branches { from to status }
powerFlow(method: NEWTON_RAPHSON) {
status
iterations
durationMs
buses { id v theta }
branches { from to loading }
losses { pMw qMvar }
}
}
}
变量:
{
"id": "net_8f3a2b"
}
响应:
{
"data": {
"network": {
"id": "net_8f3a2b",
"name": "IEEE-14",
"baseKv": 110.0,
"status": "READY",
"buses": [
{"id": 1, "type": "SLACK", "voltage": {"magnitude": 1.05, "angle": 0.0}},
{"id": 2, "type": "PV", "voltage": {"magnitude": 1.04, "angle": -0.039}}
],
"branches": [{"from": 1, "to": 2, "status": "CLOSED"}],
"powerFlow": {
"status": "CONVERGED",
"iterations": 4,
"durationMs": 12,
"buses": [
{"id": 1, "v": 1.05, "theta": 0.0},
{"id": 2, "v": 1.04, "theta": -0.039}
],
"branches": [{"from": 1, "to": 2, "loading": 0.45}],
"losses": {"pMw": 1.23, "qMvar": 5.67}
}
}
}
}
时序数据聚合查询
query GetTimeSeries($query: TimeSeriesQuery!) {
timeseries(query: $query) {
timestamp
value
tags
}
}
变量:
{
"query": {
"metric": "branch_loading",
"tags": [{"key": "network", "value": "net_8f3a2b"}, {"key": "branch", "value": "1-2"}],
"from": "2026-07-06T00:00:00Z",
"to": "2026-07-06T08:00:00Z",
"aggregation": "AVG",
"interval": "5m"
}
}
多时序并行查询
query GetMultiTimeSeries($queries: [TimeSeriesQuery!]!) {
timeseriesMulti(queries: $queries) {
timestamp
value
}
}
变量:
{
"queries": [
{"metric": "branch_loading", "from": "2026-07-06T00:00:00Z", "to": "2026-07-06T08:00:00Z", "aggregation": "AVG", "interval": "5m"},
{"metric": "bus_voltage", "from": "2026-07-06T00:00:00Z", "to": "2026-07-06T08:00:00Z", "aggregation": "AVG", "interval": "5m"}
]
}
使用片段(Fragment)
fragment NetworkBasic on Network {
id
name
status
busCount
createdAt
}
query GetNetworks {
networks(filter: {status: READY}) {
items {
...NetworkBasic
}
total
}
}
使用别名(Alias)
query GetMultipleNetworks {
net1: network(id: "net_8f3a2b") {
id
name
status
}
net2: network(id: "net_9c4d1e") {
id
name
status
}
}
Mutation 示例
创建电网
mutation CreateNetwork($input: NetworkInput!) {
createNetwork(input: $input) {
id
name
status
busCount
branchCount
createdAt
}
}
变量:
{
"input": {
"name": "IEEE-14",
"baseKv": 110.0,
"baseMva": 100.0,
"buses": [
{"id": 1, "type": "SLACK", "v": 1.05, "theta": 0.0},
{"id": 2, "type": "PV", "v": 1.04},
{"id": 3, "type": "PQ"}
],
"branches": [
{"from": 1, "to": 2, "r": 0.01938, "x": 0.05917, "b": 0.0528}
],
"tags": {"owner": "ieee"}
}
}
触发潮流计算
mutation RunPowerFlow($id: ID!, $method: PowerFlowMethod) {
runPowerFlow(id: $id, method: $method, async: false) {
taskId
status
iterations
durationMs
buses { id v theta }
branches { from to loading }
losses { pMw qMvar }
}
}
创建 Agent 并下发指令
mutation CreateAgentAndCommand($agentInput: AgentInput!, $commandInput: CommandInput!) {
createAgent(input: $agentInput) {
id
name
status
}
}
mutation SendCommand($agentId: ID!, $command: CommandInput!) {
sendCommand(agentId: $agentId, command: $command) {
commandId
status
result
durationMs
}
}
写入时序数据
mutation WriteTimeseries($points: [TimeSeriesPointInput!]!) {
writeTimeseries(points: $points) {
accepted
rejected
errors { index message }
}
}
变量:
{
"points": [
{"metric": "branch_loading", "value": 0.42, "tags": {"network": "net_8f3a2b", "branch": "1-2"}},
{"metric": "bus_voltage", "value": 1.05, "tags": {"network": "net_8f3a2b", "bus": "1"}}
]
}
Subscription 示例
订阅告警流
subscription AlarmStream($severity: Severity) {
alarmStream(severity: $severity) {
id
severity
type
message
networkId
raisedAt
source { type id }
}
}
变量:
{
"severity": "CRITICAL"
}
订阅 Agent 状态变更
subscription AgentStatus($agentId: ID) {
agentStatusChanged(id: $agentId) {
agentId
oldStatus
newStatus
reason
timestamp
}
}
订阅潮流计算进度
subscription PowerFlowProgress($taskId: ID!) {
powerFlowProgress(taskId: $taskId) {
taskId
iteration
maxIterations
mismatch
timestamp
}
}
多语言代码示例
cURL
curl -X POST http://localhost:8080/graphql \
-H "Content-Type: application/json" \
-H "Authorization: Bearer <token>" \
-d '{
"query": "query GetNetworks { networks { items { id name status } total } }"
}'
Rust
use graphql_client::{GraphQLQuery, Response};
use serde::Serialize;
#[derive(GraphQLQuery)]
#[graphql(
schema_path = "schema.json",
query_path = "queries/get_networks.graphql",
response_derives = "Debug"
)]
pub struct GetNetworks;
#[derive(Serialize)]
pub struct Vars;
#[tokio::main]
async fn main() -> anyhow::Result<()> {
let client = reqwest::Client::new();
let req_body = GetNetworks::build_query(Vars {});
let resp = client
.post("http://localhost:8080/graphql")
.bearer_auth("<token>")
.json(&req_body)
.send()
.await?;
let response_body: Response<get_networks::ResponseData> = resp.json().await?;
if let Some(data) = response_body.data {
for net in data.networks.items {
println!("{}: {} ({:?})", net.id, net.name, net.status);
}
}
Ok(())
}
Python
import requests
url = "http://localhost:8080/graphql"
headers = {
"Content-Type": "application/json",
"Authorization": "Bearer <token>",
}
query = """
query GetNetworks {
networks {
items { id name status }
total
}
}
"""
response = requests.post(url, json={"query": query}, headers=headers)
data = response.json()
for net in data["data"]["networks"]["items"]:
print(f"{net['id']}: {net['name']} ({net['status']})")
JavaScript
import { graphqlRequest } from '@eneros/sdk';
const query = `
query GetNetworks {
networks {
items { id name status }
total
}
}
`;
const data = await graphqlRequest({
endpoint: 'http://localhost:8080/graphql',
query,
token: '<bearer-token>',
});
console.log(data.networks.items);
Subscription(JavaScript)
import { createClient } from 'graphql-ws';
const client = createClient({
url: 'ws://localhost:8080/graphql',
connectionParams: {
token: '<bearer-token>',
},
});
const unsubscribe = client.subscribe(
{
query: `
subscription OnAlarm($severity: Severity) {
alarmStream(severity: $severity) {
id
severity
message
raisedAt
}
}
`,
variables: { severity: 'CRITICAL' },
},
{
next: (data) => console.log('告警:', data),
error: (err) => console.error('订阅错误:', err),
complete: () => console.log('订阅结束'),
}
);
错误处理
GraphQL 错误遵循规范,返回 HTTP 200 + errors 字段:
{
"errors": [
{
"message": "Network not found: net_unknown",
"locations": [{"line": 2, "column": 3}],
"path": ["network"],
"extensions": {
"code": "NOT_FOUND",
"trace_id": "trace_abc123",
"details": {"id": "net_unknown"}
}
}
],
"data": null
}
错误码表
| 错误码 | 含义 | 处理建议 |
|---|---|---|
UNAUTHENTICATED | 未认证 | 添加 Authorization Header |
FORBIDDEN | 权限不足 | 调整角色权限 |
NOT_FOUND | 资源不存在 | 核对 ID |
VALIDATION_FAILED | 字段校验失败 | 查看错误详情 |
CONSTRAINT_VIOLATION | 违反安全约束 | 修正电气参数 |
RATE_LIMITED | 触发限流 | 退避重试 |
QUERY_TOO_COMPLEX | 查询复杂度超限 | 拆分查询 |
QUERY_TOO_DEEP | 查询深度超限 | 减少嵌套 |
INTERNAL_ERROR | 内部错误 | 联系运维 |
POWERFLOW_DIVERGED | 潮流不收敛 | 调整初值 |
部分成功响应
当查询多个字段时,部分字段成功部分失败,data 中失败字段为 null:
{
"data": {
"network": null,
"agents": {"items": [], "total": 0}
},
"errors": [
{
"message": "Network not found",
"path": ["network"],
"extensions": {"code": "NOT_FOUND"}
}
]
}
Introspection
开发环境支持 Introspection,可查询 Schema 元信息:
query IntrospectSchema {
__schema {
queryType { name }
mutationType { name }
subscriptionType { name }
types {
name
kind
fields {
name
type { name kind }
}
}
}
}
query IntrospectType($name: String!) {
__type(name: $name) {
name
kind
fields {
name
type { name kind ofType { name kind } }
args { name type { name kind } }
}
enumValues { name }
}
}
生产环境可通过配置开启:
[graphql]
introspection = true
性能建议
- 查询深度限制为 7 层,复杂度上限 1000,防止恶意嵌套
- 时序查询建议指定
aggregation,避免返回原始高频采样点 - Subscription 自动断线重连,重连后补发最近 100 条事件
- 使用持久化查询:高频查询可预先注册,减少网络传输
# 持久化查询请求
{
"extensions": {
"persistedQuery": {
"sha256Hash": "abc123...",
"version": 1
}
}
}
相关文档
- API 总览 — 四种 API 对比
- WebSocket — Subscription 底层协议
- REST API — REST 端点对照
- 限流与配额 — 复杂度与速率限制
- eneros-graphql crate — GraphQL 服务实现