跳到主内容

版本管理

API 参考

API 版本管理

EnerOS 采用语义化版本 + URL 路径版本的双重策略,确保 API 演进过程中客户端无需频繁升级,同时允许引入破坏性变更。版本管理涵盖 REST、GraphQL、WebSocket、SSE 四种 API,每种协议的版本协商方式略有不同,但共享同一套兼容性保证与废弃流程。

版本策略

1. URL 路径版本(REST / SSE)

每个主版本通过 URL 路径区分,路径中的版本号与 EnerOS 大版本对齐:

版本路径状态发布时间废弃时间EnerOS 对应版本
/api/v1/...稳定2026-01v0.40+
/api/v2/...预览2026-09(计划)v0.50+
/api/v3/...规划中2027(计划)v1.0+
http://localhost:8080/api/v1/networks      # 当前稳定版
http://localhost:8080/api/v2/networks      # 下一主版本(开发中)

规则

  • 主版本号变更 = 破坏性变更(端点删除、字段语义变更、行为变更)
  • 次版本与补丁版本不改变 URL 路径
  • 同一主版本下,新端点/新字段通过追加方式引入,向后兼容
  • 多个主版本可同时在线,过渡期内并行支持

2. 语义化版本(SemVer)

EnerOS 整体版本遵循 MAJOR.MINOR.PATCH 规则:

变更类型示例兼容性URL 版本号说明
PATCHbug 修复、性能优化、文档更新完全兼容不变0.47.0 → 0.47.1
MINOR新增端点、新增字段、新增枚举值向后兼容不变0.47.0 → 0.48.0
MAJOR删除端点、字段语义变更、行为变更破坏性+10.47.x → 1.0.0

3. Header 版本协商(GraphQL)

GraphQL 不使用 URL 版本,通过 Header 协商 API 版本与特性集:

POST /graphql HTTP/1.1
Accept: application/graphql; version=2026-07
Content-Type: application/json
Header取值说明
Acceptapplication/graphql; version=YYYY-MM指定 API 月份版本
Acceptapplication/graphql+json; version=2026-07显式指定 JSON 响应
Schema-Version2026-07Schema 版本(与 Accept 等价)

GraphQL 版本命名采用 YYYY-MM 格式,每季度发布一个新版本:

版本状态发布时间废弃时间
2026-01已废弃2026-012026-07
2026-04已废弃2026-042026-10
2026-07当前稳定2026-072027-01
2026-10计划2026-102027-04

4. WebSocket 子协议版本

WebSocket 通过子协议(Sec-WebSocket-Protocol)协商版本:

Sec-WebSocket-Protocol: eneros.v1
子协议状态发布时间废弃时间
eneros.v1稳定2026-01
eneros.v2规划2027

客户端可在握手时声明多个子协议,服务端选择最高支持的版本:

const ws = new WebSocket('ws://localhost:8080/ws', ['eneros.v1', 'eneros.v2']);
console.log(ws.protocol); // 服务端选定的版本,如 "eneros.v1"

兼容性保证

兼容性级别

级别说明保证期
稳定(Stable)接口已固化,仅接受向后兼容变更至下一主版本发布后 12 个月
预览(Preview)接口可能变更,不建议生产使用无保证,随时可能调整
实验(Experimental)实验性功能,可能被移除无保证
废弃(Deprecated)即将下线,应尽快迁移公告后 6-9 个月

兼容性规则

以下变更视为向后兼容,不改变版本号:

变更兼容性示例
新增端点新增 GET /api/v1/topology/{id}
新增可选请求字段新增 description 可选字段
新增响应字段响应中新增 updated_at
新增枚举值status 新增 archived
字段从可选改为必填破坏性,需新版本
字段语义变更v 从标幺值改为有名值
删除端点删除 GET /api/v1/old_endpoint
删除字段删除 legacy_field
改变默认值⚠️视影响范围,可能需新版本

字段废弃标注

废弃字段通过响应头与响应体同时标注:

HTTP/1.1 200 OK
Deprecation: true
Sunset: Sun, 06 Jul 2027 00:00:00 GMT
Link: </api/v2/networks>; rel="successor-version"
Warning: 299 - "The 'bus_count' field is deprecated, use 'buses.count' instead"
响应头说明
Deprecationtrue 表示响应包含废弃字段
Sunset端点/字段的预计下线日期
Link: rel="successor-version"新版本端点位置
Warning299 状态码的人类可读警告

GraphQL 字段废弃通过 @deprecated 标注:

type Network {
  busCount: Int! @deprecated(reason: "Use 'buses.count' instead")
  buses: [Bus!]!
}

API 版本生命周期

每个主版本经历完整的生命周期:

规划 → 预览 → 稳定 → 维护 → 废弃 → 下线
  │      │       │       │       │       │
  │      │       │       │       │       └─ 返回 410 Gone
  │      │       │       │       └─ 仅安全修复,文档标注
  │      │       │       └─ 不接受新特性,仅 bug 修复
  │      │       └─ 主推版本,完整支持
  │       └─ 可用但可能变更,文档标注 "Preview"
  └─ RFC / ADR 讨论阶段
阶段持续时间支持文档标注
规划1-3 个月RFC
预览3-6 个月best-effortPreview
稳定12-24 个月完整
维护6-12 个月仅安全修复Maintenance
废弃6 个月仅安全修复Deprecated
下线410 Gone

当前版本状态

版本状态发布进入维护废弃公告下线
v1稳定2026-01
v2预览2026-09
GraphQL 2026-01废弃2026-012026-072026-072027-01
GraphQL 2026-04废弃2026-042026-102026-102027-04
GraphQL 2026-07稳定2026-07

废弃流程

废弃阶段

每个端点或字段的废弃遵循标准化流程:

阶段持续时间动作客户端感知
1. 公告期6 个月文档与响应头标注 deprecated响应头 Deprecation: true
2. 告警期3 个月调用废弃端点返回警告日志响应头 Warning: 299
3. 限流期1 个月废弃端点限流收紧至 10%频繁收到 429
4. 下线移除端点,返回 410 Gone调用失败

废弃响应示例

第 1 阶段:公告期

HTTP/1.1 200 OK
Deprecation: true
Sunset: Sun, 06 Jul 2027 00:00:00 GMT
Link: </api/v2/networks/net_8f3a2b>; rel="successor-version"
Warning: 299 - "GET /api/v1/networks/{id}/legacy_powerflow is deprecated, use POST /api/v1/networks/{id}/powerflow instead"
Content-Type: application/json

{"data": {"id": "net_8f3a2b", "bus_count": 14}}

第 4 阶段:下线

{
  "error": {
    "code": "GONE",
    "message": "Endpoint GET /api/v1/networks/{id}/legacy_powerflow has been removed. Use POST /api/v1/networks/{id}/powerflow instead.",
    "details": {
      "removed_at": "2027-07-06T00:00:00Z",
      "successor": "/api/v1/networks/{id}/powerflow",
      "doc_url": "https://docs.openeneros.com/api-reference/versioning#迁移指南"
    }
  },
  "request_id": "req_20260706_001"
}

GraphQL 废弃

GraphQL 字段废弃通过 @deprecated 指令,客户端在 Introspection 时可获取:

query {
  __type(name: "Network") {
    fields {
      name
      isDeprecated
      deprecationReason
    }
  }
}

响应:

{
  "data": {
    "__type": {
      "fields": [
        {"name": "busCount", "isDeprecated": true, "deprecationReason": "Use 'buses.count' instead"},
        {"name": "buses", "isDeprecated": false, "deprecationReason": null}
      ]
    }
  }
}

迁移指南

v1 → v2 迁移示例

字段重命名

v1 响应:

curl http://localhost:8080/api/v1/networks/net_8f3a2b
{
  "id": "net_8f3a2b",
  "name": "IEEE-14",
  "bus_count": 14,
  "branch_count": 20,
  "created_at": "2026-07-06T08:30:00Z"
}

v2 响应(字段语义化,使用嵌套结构):

curl http://localhost:8080/api/v2/networks/net_8f3a2b
{
  "id": "net_8f3a2b",
  "name": "IEEE-14",
  "topology": {
    "buses": {"count": 14},
    "branches": {"count": 20}
  },
  "metadata": {
    "created_at": "2026-07-06T08:30:00Z"
  }
}

迁移映射表:

v1 字段v2 字段说明
bus_counttopology.buses.count嵌套化
branch_counttopology.branches.count嵌套化
created_atmetadata.created_at元信息分组
vvoltage.magnitude语义化
thetavoltage.angle语义化

端点路径变更

v1 端点v2 端点说明
POST /networks/{id}/powerflowPOST /networks/{id}:runPowerFlow改用 Google API 命名规范
GET /agents/{id}/statusGET /agents/{id}:getStatus动作类端点使用冒号
POST /timeseries/batchPOST /timeseries:batchWrite动作类端点使用冒号

行为变更

行为v1v2说明
潮流默认方法newton_raphsonnewton_raphson不变
潮流默认超时30000ms60000ms增大超时
时序聚合时区UTCUTC(支持 tz 参数)增强
分页默认大小2050增大默认值
错误码命名INVALID_REQUESTINVALID_REQUEST不变

迁移步骤

  1. 审计当前 API 使用:通过审计日志统计在用端点
  2. 阅读迁移指南:对照字段映射表评估改动量
  3. 使用版本感知 SDK:SDK 自动处理版本差异
  4. 逐步切换:先在测试环境切换至 v2,验证后逐步推进生产
  5. 监控废弃告警:观察 Warning: 299 响应头,识别遗漏的废弃端点
  6. 删除 v1 调用:v1 下线前完成全部迁移

SDK 版本协商

eneros-sdk 自动处理版本协商与字段映射:

use eneros_sdk::ApiClient;

let client = ApiClient::builder()
    .endpoint("http://localhost:8080")
    .api_version("v2")           // 指定版本
    .token("<bearer-token>")
    .auto_migrate(true)           // 启用自动迁移(v1 → v2 字段映射)
    .build()?;

let network = client.networks().get("net_8f3a2b").await?;
// v2 SDK 直接返回嵌套结构
println!("{:?}", network.topology.buses.count);
from eneros_sdk import ApiClient

client = ApiClient(
    endpoint="http://localhost:8080",
    api_version="v2",
    token="<bearer-token>",
    auto_migrate=True,
)
network = client.networks.get("net_8f3a2b")
print(network.topology.buses.count)
import { ApiClient } from '@eneros/sdk';

const client = new ApiClient({
  endpoint: 'http://localhost:8080',
  apiVersion: 'v2',
  token: '<bearer-token>',
  autoMigrate: true,
});
const network = await client.networks.get('net_8f3a2b');
console.log(network.topology.buses.count);

变更日志

每次 MINOR / MAJOR 发布同步更新 CHANGELOG.md 与 API 参考文档。重大变更发布 ADR(架构决策记录)说明动机与迁移路径。

变更日志格式

# CHANGELOG

## [0.48.0] - 2026-07-06

### Added
- 新增 `GET /api/v1/topology/{id}` 端点,查询电网拓扑
- 新增 `POST /api/v1/networks/{id}:validate` 端点
- `NetworkInput` 新增 `tags` 字段
- `AgentStatus` 枚举新增 `paused`

### Changed
- `POST /networks/{id}/powerflow` 默认超时从 30s 调整为 60s
- 分页默认 `page_size` 从 20 调整为 50

### Deprecated
- `Network.bus_count` 字段废弃,使用 `Network.buses.count` 替代
- `GET /agents/{id}/status` 端点废弃,使用 `GET /agents/{id}` 替代

### Fixed
- 修复潮流计算在孤岛场景下 panic 的问题
- 修复 WebSocket 重连后事件丢失的问题

### Security
- 升级 `rustls` 至 0.23.x,修复 CVE-2026-1234

ADR(架构决策记录)

重大变更发布 ADR,记录决策背景、方案对比与最终选择:

docs/adr/
├── 0001-url-path-versioning.md
├── 0002-graphql-header-versioning.md
├── 0003-websocket-subprotocol-versioning.md
├── 0004-v2-field-nesting.md
└── 0005-deprecation-process.md

ADR 模板:

# ADR-0004: v2 字段嵌套化

- 状态:已接受
- 日期:2026-06-15
- 决策者:API 工作组

## 背景
v1 响应字段扁平化,导致字段数量增长后难以理解字段归属...

## 方案对比
- 方案 A:保持扁平 + 前缀(`network_bus_count`
- 方案 B:嵌套分组(`topology.buses.count`)✅
- 方案 C:GraphQL 化(全部走 GraphQL)

## 决策
采用方案 B,理由...

## 影响
- 客户端需更新字段访问路径
- SDK 提供 auto_migrate 自动映射

兼容性测试

EnerOS 维护兼容性测试套件,确保版本演进不破坏既有契约:

测试类型说明工具
契约测试验证响应结构符合 Schemaschemathesis
快照测试对比响应 JSON 快照insta
集成测试端到端调用验证testcontainers
回归测试旧版本端点不被破坏cargo test

兼容性测试在 CI 中自动运行,任何破坏性变更都会被检测并阻止合并。

多版本共存

EnerOS 支持多主版本并行运行,过渡期内 v1 与 v2 同时可用:

http://localhost:8080/api/v1/networks   # v1 端点
http://localhost:8080/api/v2/networks   # v2 端点

数据层共享,仅接口层不同。同一资源在 v1 与 v2 中的 ID 一致,可跨版本引用。

多版本配置

[api]
enabled_versions = ["v1", "v2"]
default_version = "v1"
v1_sunset_at = "2027-07-06"  # v1 计划下线日期
v2_preview = true              # v2 是否为预览版

版本协商最佳实践

客户端固定版本

生产环境建议固定 API 版本,避免自动升级带来的意外:

let client = ApiClient::builder()
    .endpoint("http://localhost:8080")
    .api_version("v1")        // 固定 v1
    .pin_minor_version("0.47") // 固定 minor 版本
    .build()?;

监控废弃告警

客户端应监控 WarningDeprecation 响应头,提前发现需迁移的端点:

import logging

response = client.get("/networks/net_8f3a2b")
if response.headers.get("Deprecation") == "true":
    sunset = response.headers.get("Sunset", "unknown")
    warning = response.headers.get("Warning", "")
    logging.warning(f"调用了废弃端点,下线时间: {sunset}, 详情: {warning}")

渐进式迁移

1. 全量使用 v1
2. 新功能使用 v2(混合阶段)
3. 逐步将 v1 调用迁移至 v2
4. v1 下线后,全部使用 v2

相关文档