跳到主内容

GraphQL API

API 参考

GraphQL API

EnerOS 通过 eneros-graphql crate 提供 GraphQL 接口,端点为 POST /graphql。GraphQL 适合需要灵活字段选取、多类型聚合查询的场景,避免 REST 的 over-fetching / under-fetching 问题。EnerOS 的 GraphQL 实现基于 async-graphql,完全异步、零拷贝,与内核共享同一套系统调用。

端点信息

项目取值
URLhttp://localhost:8080/graphql
方法POST
Content-Typeapplication/json
认证Bearer Token(Header)或 mTLS
订阅端点ws://localhost:8080/graphql(WebSocket Subscriptions 协议)
Introspection开发环境开启,生产环境默认关闭
查询深度限制7 层
复杂度上限1000
查询超时30 秒
单次别名限制20

请求格式

GraphQL 请求体统一为 JSON,包含以下字段:

字段类型必填默认值说明
querystringGraphQL 查询字符串
variablesobjectnull变量字典
operationNamestring当 query 包含多个操作时指定
extensionsobject扩展字段(如持久化查询)

完整 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
    }
  }
}

相关文档