REST API (v1)
PlantPulse Edge 的 v1 REST API 手册。这是用于界面/外部系统处理 OPC、标签、监控、外部传输健康检查的标准接口。
| 项目 | 值 |
|---|---|
| Base URL | http(s)://<edge-host>/api/v1 |
| Content-Type | application/json; charset=utf-8 |
| 认证 | 强制 API Key (edge.rest.api.auth=true) — X-API-Key / Authorization: Bearer, 或通过登录会话。?api_key= 将以 401 拒绝 |
| Audit 日志 | ApiAccessLogFilter — 将所有 /api/* 调用记录为单行 (方法/路径/状态/持续时间/IP/用户/auth 模式) |
| 主控制器 | plantpulse.app.edge.api.v1.* (OpcAPI, TagAPI, AppAPI, SystemAPI, MonitoringAPI) |
| 错误 envelope | {data, meta, errors} — filter/advice 路径也相同 |
| HTTPS | edge.rest.api.https_only=true 默认。拒绝明文 HTTP client 调用或遵循 Tomcat HTTPS 重定向策略 |
认证 (ApiAuthFilter)
在 app.properties 中通过 edge.rest.api.auth=true 激活。密钥为 edge.rest.api.key,在运行环境下通过 EDGE_REST_API_KEY_FILE 或 EDGE_REST_API_KEY 注入。
满足以下三种情况中的任意一项即可通过:
A. 登录会话通过 (浏览器)
已通过 /login/form 登录的用户 — 如果会话中存在 _USER_LOGIN 属性 (JSONObject),ApiAuthFilter 将直接执行 chain.doFilter()。所有从界面 (JSP) 通过 ajax 调用的 /api/* 均无需额外密钥即可运行。
B. X-API-Key 请求头 (推荐)
EDGE_REST_API_KEY="$(tr -d '\r\n' < /run/secrets/edge-rest-api-key)"
cfg="$(mktemp)"
trap 'rm -f "$cfg"' EXIT
printf 'header = "X-API-Key: %s"\n' "$EDGE_REST_API_KEY" > "$cfg"
curl --config "$cfg" https://edge.example.com/api/v1/tag/TAG_LS_XBC_0001/value
C. Authorization: Bearer <key> 请求头
EDGE_REST_API_KEY="$(tr -d '\r\n' < /run/secrets/edge-rest-api-key)"
cfg="$(mktemp)"
trap 'rm -f "$cfg"' EXIT
printf 'header = "Authorization: Bearer %s"\n' "$EDGE_REST_API_KEY" > "$cfg"
curl --config "$cfg" https://edge.example.com/api/v1/tag/TAG_LS_XBC_0001/value
D. ?api_key=<key> 查询参数 — 已拒绝
由于密钥以明文形式出现在 URL 中,可能会残留在访问日志/代理缓存/浏览器历史记录中,因此 v1 版本将以 401 拒绝。请使用请求头方式 (B/C)。
失败响应
HTTP/1.1 401 Unauthorized
Content-Type: application/json; charset=utf-8
{
"data": null,
"meta": {
"timestamp": 1778069486934,
"status": "ERROR",
"http_status": 401,
"request_id": "3b89b8f1-0fc1-4ab4-b4eb-d12dc314d500"
},
"errors": [
{
"code": "UNAUTHORIZED",
"message": "Invalid or missing X-API-Key."
}
]
}
密钥比较采用 constantTimeEquals() (防止时间攻击)。
edge.rest.api.auth=false (开发 / 临时)
关闭认证后,ApiAuthFilter 将立即执行 chain.doFilter() — 允许从外部无需密钥即可调用。运行网络建议始终设为 true。
Audit 日志 (ApiAccessLogFilter)
将所有 /api/* 调用记录为单行日志 — 包含认证失败 (401) 记录,以留下 audit 轨迹 (过滤器链顺序: ApiAccessLogFilter → ApiAuthFilter)。
格式
[API_ACCESS] method=GET path=/api/v1/tag/TAG_MS_1000/value query= status=200 duration=12ms
ip=100.127.147.98 user=admin auth=session ua=Mozilla/5.0...
| 字段 | 含义 |
|---|---|
method | HTTP 方法 |
path | request URI (不含查询参数) |
query | 查询字符串 — 被拒绝的 ?api_key=... 部分通过 *** 掩码处理 |
status | HTTP 响应代码 |
duration | 处理时间 (ms) |
ip | 默认为 RemoteAddr。仅在部署了 edge.rest.api.trust_x_forwarded_for=true 的 reverse proxy 时为 X-Forwarded-For 第一个 IP |
user | session 用户则为 user_id / apikey 则为 XXXX*** (len=16) (前 4 位 + 长度) / 匿名则为 - |
auth | session (浏览器登录) / apikey (X-API-Key/Bearer) / deprecated_query (被拒绝的 ?api_key= 尝试) / none (缺失密钥或匿名) |
ua | User-Agent (超过 80 个字符时 … 截断) |
error | 发生异常时追加 error=ClassName |
日志级别分级
| 状态 | 级别 | 意图 |
|---|---|---|
| 5xx 或 throwable | ERROR | 服务器错误 — 立即报警 |
| 401, 403 | WARN | 认证/权限拒绝 — 优先 audit |
| 其他 (2xx, 3xx, 4xx 中的 4xx 一般情况) | INFO | 日常调用 |
应用场景
- 谁在发送错误密钥 — 通过
grep '\[API_ACCESS\].*status=401'确认 401 轨迹 - 5xx 回归追踪 —
grep '\[API_ACCESS\].*status=5'+error=字段 - 调用频率 / 用户统计 — 按 auth=apikey/session 分组,按 IP 分析
- 响应延迟慢查询 — 提取
duration=较大行
密钥明文绝不会出现在任何日志中 — 通过查询掩码 + 用户字段前 4 位进行识别。这是在入侵事故分析所需的识别度与防止明文泄露之间取得的平衡。
web.xml 映射顺序:ApiAccessLogFilter 在前,然后是 ApiAuthFilter。由于 AccessLog 在 finally 块中记录 status,因此即使是认证失败的 401 调用也会被完整记录在 audit 中。如果顺序颠倒,401 调用将在 access log 中缺失。
响应 envelope
v1 REST API 响应使用统一的 envelope 封装 (RestApiSupport, ApiEnvelopeWriter)。
正常响应:
{
"data": { },
"meta": {
"timestamp": 1778069486934,
"status": "OK",
"request_id": "f818abd0-eb0f-4497-9b07-7ccc94b2e2d7"
}
}
错误响应:
{
"data": null,
"meta": {
"timestamp": 1778069486934,
"status": "ERROR",
"http_status": 400,
"request_id": "3b89b8f1-0fc1-4ab4-b4eb-d12dc314d500"
},
"errors": [
{
"code": "VALIDATION_FAILED",
"message": "Validation failed"
}
]
}
旧的 RPC-style envelope (result, _session_id, _user_id) 不在 REST v1 中使用。
1. 通用协议
在查看端点之前,以下是 所有 /api/v1/* 共同遵守的事项。如果不了解这些,即使阅读单个端点文档,客户端实现也会出错。
1.1 运行请求头
| 请求头 | 方向 | 用途 |
|---|---|---|
X-API-Key | 请求 | 认证。?api_key= 查询将被废弃(401) |
Authorization: Bearer <key> | 请求 | API 密钥的替代方案 |
X-Request-Id | 请求 | 用于追踪。将在响应 meta.request_id 中回显。若不提供则由服务器生成 |
Idempotency-Key | 请求 | 防止 POST 重复提交。TTL 10 分钟。使用相同密钥重试时,将返回缓存的响应及 Idempotency-Replay: true |
If-Match | 请求 | 乐观并发控制。若与 GET 响应的 ETag 不同,则返回 412 |
ETag | 响应 | weak validator(W/"<hex>")。目前仅返回 GET /api/v1/opc/{opcId} |
Location | 响应 | POST 创建(201) 时新资源的 URI |
X-RateLimit-Limit · -Remaining · -Reset | 响应 | 速率限制信息 |
Retry-After | 响应 | 429 时需等待的秒数 |
速率限制通过 edge.rest.api.rate_limit.enabled 和 edge.rest.api.rate_limit.per_second (默认 1000) 进行调节。
Idempotency-Key现场线路可能会中断。未收到响应的 POST 并非「未成功」,而是「未知状态」 — 直接再次发送可能会创建两个 OPC。使用相同密钥发送,网关将原样返回第一次的结果。
1.2 分页
列表端点的通用查询参数。
| 查询 | 默认值 | 说明 |
|---|---|---|
page | 0 | 从 0 开始计算的页码 |
size | 0 | 若为 0 则返回全部。 若为 1..1000 则截断相应数量并填充 meta.pagination |
若 size 超过 1000 或为负数,或 page 为负数,则返回 400 INVALID_RANGE。
1.3 ID 规则
| 对象 | 正则表达式 |
|---|---|
site_id | ^SITE_[A-Z0-9_]{1,60}$ |
opc_id | ^OPC_[A-Z0-9_]{1,60}$ |
tag_id | ^TAG_[A-Z0-9_]{1,60}$ |
data_type 仅有 Boolean · Integer · Long · Float · Double · String 这六种 (与 Cassandra ENUM 一致)。opc_type 为驱动程序拥有的协议常量白名单 (LS · OPCUA · MODBUS · MODBUS_RTU · HTTP · MELSEC · S7 · EIP …),白名单正本位于 GET /v3/api-docs。白名单之外的值将被以 field_errors[].reason = "not_in_whitelist" 拒绝。
2. 端点一览
共 52 个。OpenAPI 正本位于网关的 GET /v3/api-docs,如需在界面查看,请访问 /public/swagger-ui/index.html。
2.1 OPC — 8
| Method | URL | 说明 | 响应 |
|---|---|---|---|
| GET | /api/v1/opc | OPC 列表 + 运行时状态 + 60 分钟趋势 | 200 · 400 |
| POST | /api/v1/opc | OPC 注册。可通过 tag_list 同时添加标签 | 201 · 400 · 409 |
| GET | /api/v1/opc/{opcId} | 详情 + ETag。通过 ?include=tags 同时返回标签数组 | 200 · 404 |
| PUT | /api/v1/opc/{opcId} | 修改 (支持 If-Match) | 200 · 404 · 412 |
| PATCH | /api/v1/opc/{opcId} | 部分修改 — 仅更改发送的字段 | 200 · 400 · 404 · 412 |
| DELETE | /api/v1/opc/{opcId} | 删除 OPC + 所有子标签 | 200 · 404 · 412 |
| POST | /api/v1/opc/{opcId}/start | 开始采集 | 200 · 404 |
| POST | /api/v1/opc/{opcId}/stop | 停止采集 | 200 · 404 |
POST 必需 7 字段 — site_id · opc_id · opc_name · opc_type · opc_agent_ip · opc_agent_port(1~65535) · timecycle(100ms 以上)。违规将一次性返回 (field_errors[])。
通过 tag_list 同时注册标签。 通常一个连接包含数十个标签,如果分两次注册,会出现 OPC 已创建但无标签的中间状态。请一次性发送 — 也可以先发送 OPC,后续通过 POST /api/v1/opc/{opcId}/tag 逐个添加。
2.2 Tag — 10
| Method | URL | 说明 | 响应 |
|---|---|---|---|
| GET | /api/v1/tag | 所有标签列表 | 200 · 400 |
| GET | /api/v1/opc/{opcId}/tag | 单个 OPC 的标签列表 | 200 · 400 |
| POST | /api/v1/opc/{opcId}/tag | 标签单笔注册 | 201 · 400 · 409 |
| GET | /api/v1/tag/{tagId} | 标签详情 | 200 · 404 |
| PUT | /api/v1/tag/{tagId} | 标签修改 | 200 · 404 |
| DELETE | /api/v1/tag/{tagId} | 标签删除 | 200 · 404 |
| GET | /api/v1/tag/{tagId}/value | 最新值。若为 ?fresh=true 则从设备重新读取 | 200 · 404 · 503 |
| POST | /api/v1/tag/{tagId}/value | 写入值 + 写入后再次读取(actual_read) | 200 · 400 · 404 · 503 |
| POST | /api/v1/tag-values:read | 一次性获取多个标签的缓存值(最多 100 条) | 200 · 400 |
| GET | /api/v1/tag/{tagId}/points | 时序查询 + 降采样 | 200 · 400 · 500 |
POST 必需 6 字段 — site_id · opc_id(从路径自动获取) · tag_id · tag_name · data_type · plc_address。
points 的查询参数: from·to(epoch ms, from < to) · minutes(默认 10, 11440) · 5000 — 超过则等间隔抽样提供)。limit(默认 600, 1
写入失败的代码映射 — 标签缺失 404 TAG_NOT_FOUND · 连接中断 503 DISCONNECTED · 采集停止 503 OPC_STOPPED · 只读标签 400 RW_VIOLATION · 类型不匹配 400 TYPE_MISMATCH · 其他 500 INTERNAL_ERROR。
2.3 应用 (Docker 容器) — 9
| Method | URL | 说明 | 响应 |
|---|---|---|---|
| GET | /api/v1/app | 容器列表 | 200 · 400 |
| POST | /api/v1/app | 镜像 pull 后运行 | 201 · 400 · 409 · 502 |
| GET | /api/v1/app/{name} | docker inspect 结果 | 200 · 404 |
| DELETE | /api/v1/app/{name} | 删除 (若为 ?image=true 则包含镜像) | 200 · 404 |
| POST | /api/v1/app/{name}/start | 开始 | 200 · 404 |
| POST | /api/v1/app/{name}/stop | 中止 | 200 · 404 |
| POST | /api/v1/app/{name}/restart | 重启 | 200 · 404 |
| GET | /api/v1/app/{name}/logs?lines=N | 最后 N 行 (1~5000, 默认 200) | 200 · 404 |
| GET | /api/v1/app/{name}/stats | CPU · 内存 · 网络 · 磁盘快照 | 200 · 404 |
name 和 image 必须通过正则表达式 + 屏蔽 Shell 元字符检查 (; & | \ $ ( ) < > \ " ' 空格). memory_limit_mb는 16 이상,cpu_limit은 0.1 이상,network는bridge·host·none`。
部署失败将告知阶段 — pull 失败 502 DOCKER_PULL_FAILED, run 失败 502 DOCKER_RUN_FAILED, 同名容器已存在 409 CONTAINER_EXISTS。
2.4 系统 — 12
| Method | URL | 说明 | 认证 |
|---|---|---|---|
| GET | /api/v1/system/health | liveness + 8 种组件检查 | 不必要 |
| GET | /api/v1/system/ready | readiness (监控 + V5 客户端) | 不必要 |
| GET | /api/v1/system/version | 版本 · 构建日期 · 镜像标签 · 容器状态 | 不必要 |
| GET | /api/v1/system/time | 网关当前时间 (epoch ms) | 必要 |
| POST | /api/v1/system/time/sync | 同步为平台时间 ({"platform_time":…}) | 必要 |
| GET | /api/v1/system/logs | 最近日志 (JSON 格式) | 必要 |
| GET | /api/v1/system/logs/download | 以 tar.gz 形式提供日志目录 | 必要 |
| GET | /api/v1/system/events | SSE 流 (text/event-stream) | 必要 |
| GET | /api/v1/system/upgrade/check | 与下载服务器最新版本对比 | 必要 |
| POST | /api/v1/system/upgrade | 触发升级 | 必要 |
| GET | /api/v1/system/upgrade/status | 升级进行状态 (polling) | 必要 |
| POST | /api/v1/system/firmware | 触发固件更新 | 必要 |
| POST | /api/v1/system/reboot | 5 秒后主机重启 | 强制 |
| POST | /api/v1/system/restart | 3 秒后 Edge Agent 重启 | 强制 |
reboot · restart 无法«关闭»认证即使通过 edge.rest.api.auth=false 关闭了认证,这两项仍要求会话登录或 API 密钥。因为如果任何人都能调用,其本身将导致服务拒绝。
2.5 监控 · 边缘 — 4
| Method | URL | 说明 |
|---|---|---|
| GET | /api/v1/monitoring | 运行时间 · OPC 数量 · 注册标签 · 扫描速度 |
| GET | /api/v1/edge | edge_id · site_id · 版本 · 主机名 · 模式 |
| GET | /api/v1/edge/status | 边缘状态摘要 |
| GET | /api/v1/transfer | 各个传出传输 (MQTT · SparkplugB · 平台 API) 状态 |
若 monitoring 在启动后监控尚未就绪,则返回 503 MONITOR_NOT_READY — 这并非故障,而是「尚未就绪」。
2.6 冗余 (HA) — 4
| Method | URL | 说明 |
|---|---|---|
| GET | /api/v1/ha/status | 此边缘的写入权限 · 模式 (manual/auto) · 开始时间 · 对端网关状态 |
| PUT | /api/v1/ha/write-authority | 提升或降低 此边缘的 写入权限 ({"enabled":true}) |
| PUT | /api/v1/ha/failover/override | 手动干预自动故障转移 — 仅在 auto 模式下(否则 409 NOT_AUTO_MODE) |
| POST | /api/v1/ha/definitions | 将标签定义复制给对端 |
写入权限 — 读取前须知
拦截内容: 无权限的边缘将拒绝实际 PLC 写入 (WRITE_NOT_AUTHORIZED)。配置键为 edge.plc.write_authority,若不存在则为 true — 新安装的网关默认持有权限。
HTTP 采集不经过此关卡 — 但并非«始终»如此opc_type 为 HTTP 的连接是入站采集而非发往设备的写入。因此,即使在降低权限的边缘,HTTP 采集仍将继续 — 因为不存在双重写入风险。
⚠ 启动后,该 HTTP 写入可能会被暂时拒绝。 关卡从缓存读取 opc_type,若缓存未填充或查询失败,将视为「实际 PLC」并拦截(此为向安全方向倾斜的预期行为)。且此类查询失败不会记录日志。
因此,若遇到「本应继续的 HTTP 被拒绝」,请首先检查是否为启动初期。缓存填充后将自动恢复 — 无需修改配置。
此调用仅更改自身网关。不对对端进行询问、锁定或接管。不存在协调代码。
因此,实际上可能出现两个网关同时持有权限的状态。请遵守以下顺序:
- 降低 A 的权限 —
PUT /api/v1/ha/write-authority {"enabled":false} - 确认 A 为
false—GET /api/v1/ha/status - 提升 B 的权限
若顺序颠倒,中间会产生一个两者均为 true 的窗口期,导致两个网关同时向同一个 PLC 写入。
虽然 GET /api/v1/ha/status 的 peer.write_authority 会显示对端值,但那仅是观察而非强制 — 即使对端持有权限,此调用也不会被拦截。
persisted:false 并非「失败」,而是「重启后将消失»切换分为两步:① 实时生效 — 关卡立即看到新值 (无需重启)。② app.properties 持久化 — 若此步失败,① 仍维持并报告 persisted:false。
这意味着当前虽然生效,但下次重启后将恢复旧值。现场最易出现的问题就是认为已降低权限而重启,结果权限再次开启。请在重启前解决原因 — 通常为 /etc/kopens/conf 的写入权限。诊断信息不在响应中,而是在服务器日志中:
[HA] write-authority persist to app.properties failed — applied live only
since 为「控制权最后转移时间»。仅在值实际发生变更时更新,若发送相同值则不跳动。
审计记录始终保留 — 变更时记录为 WARN,未变更则在 INFO 中追加 [no-op],且两者均包含调用者 IP 和账号。
[HA][AUDIT] write-authority false -> true by 10.0.0.5 (admin)
⚠ 转移历史和平台诊断推送仅在变更时触发,即使这两者失败,切换也不会失效 (仅记录在日志中)。「无历史记录»不代表«未变更» — 请通过 status 确认。
边缘告知的两种状态。 监控配对的一方将在日志中记录不一致状态。它不修改配置 — 由运营人员修改。
[HA] DUAL WRITE AUTHORITY detected — both edges have edge.plc.write_authority=true.
[HA] NO WRITE AUTHORITY — both edges have edge.plc.write_authority=false.
后方状态为任何地方都无法进行 PLC 写入 — 属于「降低两者权限后忘记提升」的情况。
故障转移手动干预 — PUT /api/v1/ha/failover/override
使自动故障转移停止对该边缘的判定并固定其角色。解除后自动判定恢复。
role | 含义 |
|---|---|
"WRITER" | 继续发送心跳并持有权限 |
"STANDBY" | 停止写入并放弃权限 |
null | 解除干预 — 回到自动判定状态 |
{ "data": { "override": "WRITER", "previous": null }, "meta": { "status": "OK" } }
- 仅限
auto模式。若自动故障转移未运行,则返回 409NOT_AUTO_MODE— 前述write-authority在两种模式下均可用,若在手动运行,请使用前者。 WRITER·STANDBY不区分大小写。- 若值非三者之一,则返回 400
VALIDATION_FAILED及field_errors:[{field:"role", reason:"invalid_value"}]。
时间同步 — POST /api/v1/system/time/sync
{ "platform_time": 1789780169674 }
为 epoch 毫秒 (正数),无论是数字还是数字字符串均可接受。若省略 platform_time,则通过网关配置的 NTP 源进行同步 — 发送值与省略值的行为截然不同。
响应格式为 {"applied":true,"method":"system","before":…,"after":…}。通过比较 before/after,可得出实际偏移量。
由于此调用会更改系统,因此在查看正文前先进行认证。缺失密钥或会话返回 401,认证通过后正文错误则返回 400 VALIDATION_FAILED。请勿与顺序相反的 API 混淆 — 若对错误正文返回 401,则该响应与正文无关。
2.7 备份 · 恢复 — 2
| Method | URL | 说明 |
|---|---|---|
| POST | /api/v1/connect/backup | 将所有 OPC + 所有标签导出为一个 JSON |
| POST | /api/v1/connect/restore | 将该 JSON 原样应用 (upsert) |
对称操作 — 将 backup 响应原样发送至 restore 正文即可。请务必检查 restore 响应的 skipped[] : 即使个别行失败,整体不会失败,而是跳过并仅递减计数。
2.8 计算公式 — 1
| Method | URL | 说明 |
|---|---|---|
| POST | /api/v1/tags/formula/preview | 在保存计算公式前预览结果 — 计算公式 |
2.9 免认证开放网络
仅此网络可通过 ApiAuthFilter。不输出任何 OPC 或标签信息。
GET /api/v1/system/health · GET /api/v1/system/ready · GET /api/v1/system/version · GET /api/health (无版本别名)
此网络即使没有 X-API-Key,或密钥错误,也会返回 200。因此,不能使用此路径确认密钥是否正确更改 — 请使用如 GET /api/v1/opc 等需要密钥的路径进行确认。
/health 检查八个槽位。若八个全部为 UP 则返回 status=UP + 200,只要有一个不符则返回 status=DEGRADED + 503。
{
"data": {
"status": "UP",
"uptime_ms": 1720008,
"components": {
"cassandra": "UP", "redis": "UP", "mqtt": "UP", "node_red": "UP",
"opc_ua": "UP", "edge_core": "UP", "tse": "UP", "grafana": "UP"
}
},
"meta": { "timestamp": 1778925572946, "request_id": "..." }
}
🔴 503 响应中仍包含 components。这就是诊断的全部 — 若仅看状态码而丢弃正文,将无法得知八个槽位中哪个已失效。
三个槽位仅看名称容易产生误解,特此单独说明:
| 槽位 | 实际检查内容 |
|---|---|
edge_core | readiness — 采集器/监控是否启动。启动竞态期间为 DOWN,导致 health 为 503。与平台连接无关,在 STANDALONE 网关中亦有意义 |
opc_ua | 端口并非固定。遵循 app.properties 的 opc.ua.server.tcp.port (开发网关 4840, 默认 12000)。曾因硬编码为 12000 导致开发网关永久报告为 DOWN |
grafana | 2026-07 镜像起为 HTTPS。若用明文 TCP 探测,Grafana 每 10 秒会在日志中记录 TLS handshake error … EOF — 因此网关先使用完整执行握手的探针,并将 TCP 确认作为旧版协议栈的后备方案 |
cassandra(9042) · redis(6379) · mqtt(1883) · node_red(1880) · tse(7800) 为固定端口的 TCP 确认。
/version 示例:
{
"data": {
"product_name": "PlantPulse Edge",
"version": "2026",
"build_date": "20260523",
"image_tag": "2026-20260523",
"container_mode": true
},
"meta": { "timestamp": 1778925572946, "request_id": "..." }
}
OTA upgrade.sh 在 300 秒内探测 /api/health 以判断自动回滚,Docker HEALTHCHECK 亦使用此路径。
2.10 非 API 路径 — /ui/opcua/* · /config/*
网关中包含界面所使用的路径。新客户端请勿使用。
/ui/opcua/info·/tree·/history— 仅限内置 OPC UA 服务器界面。产品 v1 API 正本已将其宣布为废弃。虽然代码仍有响应,但不再属于契约内容。/config/load·/save·/restart·/reboot·/upgrade·/firmware·/backup·/temp-clean·/platform/test— 管理员界面使用的路径。请使用执行相同功能的/api/v1/system/*— 其 envelope 和错误代码均遵循 v1 协议。/api/http(APIController) — 废弃。请使用POST /api/v1/tag/{tagId}/value。
3. 请求 / 响应示例
以下全部为当前 envelope。原文档中 {"result":"OK", ...} 形式的示例因与产品不符已被删除 — 按照该形式编写的客户端目前无法运行。
3.1 OPC 注册
curl -sk -X POST "https://<엣지>/api/v1/opc" \
-H "X-API-Key: <key>" -H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{
"site_id": "SITE_00001",
"opc_id": "OPC_LS_XBM_0001",
"opc_name": "1라인 LS XBM",
"opc_type": "LS",
"opc_agent_ip": "192.168.0.10",
"opc_agent_port": 2004,
"timecycle": 1000,
"tag_list": [
{ "site_id": "SITE_00001", "opc_id": "OPC_LS_XBM_0001",
"tag_id": "TAG_LS_XBM_0001", "tag_name": "1호기 토출압력",
"data_type": "Float", "plc_address": "%MW100" }
]
}'
tag_list 可以省略 — 省略后仅创建 OPC。
201 + Location: /api/v1/opc/OPC_LS_XBM_0001:
{
"data": { "info": { "opc_id": "OPC_LS_XBM_0001", "opc_name": "1라인 LS XBM" } },
"meta": { "timestamp": 1778925572946, "status": "OK", "request_id": "..." }
}
opc_agent_port 和 timecycle 可以发送为数字由于存储层的相应列为字符串,服务器接收后会进行转换。发送数字并接收到字符串响应属于正常现象。
3.2 删除
{
"data": { "deleted": true, "opc_id": "OPC_X" },
"meta": { "timestamp": 1778925572946, "status": "OK", "request_id": "..." }
}
DELETE 亦返回 200 + 正文。尽管 Swagger 注释中可能标注为 204,但实际行为为 200 — v1 协议要求所有方法均返回 envelope。
3.3 失败响应示例
POST /api/v1/opc/OPC_NONE/start — 404:
{
"meta": { "timestamp": 1778925572946, "status": "ERROR", "request_id": "..." },
"errors": [ { "code": "OPC_NOT_FOUND", "message": "OPC OPC_NONE 가 존재하지 않습니다." } ]
}
完全不存在 data (字段 null 在序列化时被省略)。请勿通过 data 的有无来判定成败,应通过 HTTP 状态或 meta.status 判定。
3.4 一次获取多个标签值
curl -sk -X POST "https://<엣지>/api/v1/tag-values:read" \
-H "X-API-Key: <key>" -H "Content-Type: application/json" \
-d '{ "tag_ids": ["TAG_LS_XBM_0001", "TAG_LS_XBM_0002"] }'
{
"data": [
{ "tag_id": "TAG_LS_XBM_0001", "value": "1234", "timestamp": "2026-05-15T11:32:45.000Z", "quality": "SUCCESS" },
{ "tag_id": "TAG_LS_XBM_0002", "value": null, "timestamp": null, "quality": "PENDING" }
],
"meta": { "timestamp": 1778925572946, "status": "OK", "request_id": "..." }
}
🔴 请注意 quality。PENDING 并非「值为 0」,而是**「尚未被读取过»。若将其误读为 0,将导致未被关注的设备被报告为«正常»**。
若数组为空或超过 100 条,则返回 400(MISSING_FIELD / TOO_MANY)。
3.5 重启
{
"data": { "message": "reboot scheduled in 5 seconds" },
"meta": { "timestamp": 1778925572946, "status": "OK", "request_id": "..." }
}
返回 202 — 含义并非«已完成」,而是**«决定执行»**。在收到响应的时间点,系统仍处于运行状态。
3.6 尚未就绪
GET /api/v1/system/ready — 503:
{
"meta": { "timestamp": 1778925572946, "status": "ERROR", "request_id": "..." },
"errors": [ { "code": "NOT_READY", "message": "엣지의 일부 구성요소가 준비되지 않았습니다." } ]
}
3.7 备份与恢复是对称的
{
"data": {
"opcs": [ { "opc_id": "OPC_LS_XBM_0001", "opc_name": "..." } ],
"tags": [ { "tag_id": "TAG_LS_XBM_0001", "opc_id": "OPC_LS_XBM_0001" } ],
"count": { "opc": 1, "tag": 1 },
"exported_at": 1778925572946
},
"meta": { "timestamp": 1778925572946, "status": "OK", "request_id": "..." }
}
将 data 原样发送至 POST /api/v1/connect/restore 的正文即可。请务必检查恢复响应的 skipped[] — 即使个别行失败,整体仍返回 200,仅跳过的项记录于此。若仅查看 count,可能导致部分内容未恢复而未被察觉。
4. 错误代码
🔴 v1 通过 HTTP 状态告知失败。旧的 RPC 模式 (将 {result:"ERROR"} 封装在 HTTP 200 中) 已被废弃。不看状态码而寻找正文 result 的客户端将把所有失败视为成功。
请采用以下两种判定方式之一 — HTTP 状态码,或 meta.status ("OK" / "ERROR")。无需检索 errors[],通过一个键即可区分。
| code | HTTP | 含义 |
|---|---|---|
MISSING_BODY | 400 | 请求正文为空 |
MISSING_FIELD | 400 | 缺失必需字段 |
INVALID_JSON | 400 | JSON 解析失败 |
INVALID_RANGE | 400 | page·size·from~to 范围违规 |
INVALID_ARGUMENT | 400 | 一般参数错误 (镜像·卷白名单等) |
INVALID_NAME | 400 | 容器名称包含注入字符 |
VALIDATION_FAILED | 400 | 多个字段同时出错 — 请查阅 field_errors[] |
TOO_MANY | 400 | 批量请求上限 (100 条) 超出 |
RW_VIOLATION · TYPE_MISMATCH | 400 | 标签写入失败 — 只读标签 / 类型不匹配 |
UNAUTHORIZED | 401 | 认证缺失或无效 |
HTTPS_REQUIRED | 403 | 使用了明文 HTTP 访问 — 并非密钥问题 |
OPC_NOT_FOUND · TAG_NOT_FOUND · APP_NOT_FOUND | 404 | 目标不存在 |
CONTAINER_EXISTS | 409 | 同名容器已存在 |
PRECONDITION_FAILED | 412 | If-Match 的 ETag 不匹配 — 期间已被他人修改 |
START_FAILED · STOP_FAILED | 422 | OPC 采集控制失败 |
DOCKER_PULL_FAILED · DOCKER_RUN_FAILED | 502 | Docker 命令执行失败 |
MONITOR_NOT_READY · NOT_READY · DISCONNECTED · OPC_STOPPED | 503 | 尚未就绪/已断开 — 并非请求错误 |
MISCONFIGURED | 500 | edge.rest.api.key 为空 |
INTERNAL_ERROR · *_FAILED | 500 | 其他内部错误 |
DISCONNECTED · OPC_STOPPED · MONITOR_NOT_READY 是并非请求错误,而是当前无法执行。应在稍后重新发送相同请求。若像处理 400 那样尝试「修改后重试」,将在无需修改的情况下陷入重复尝试。
5. 运行笔记
- 标签单笔查询:
APP_TAG.xml的R03(WHERE TAG_ID = :tag_id ALLOW FILTERING)。 - OPC 注册 · 标签注册后,
ConnectService.restartComponents()将重新读取缓存并重启 OPC-UA 服务器 — 这导致注册后采集会短暂中断。 - 写入值 (
tagWriteByTagId): HTTP 采用HTTPDriver.bind(), 其他采用driver.write(ProtocolAddress)。失败时 (ok=false) 包含友好提示 (如 EIP 固件兼容性)。write 后立即 read 的结果将通过actual_read共同返回 (HTTP 除外)。 - 传输健康 (
/api/v1/transfer): 汇总TransferRegistry中注册的所有 outbound 组件的getStatus()。Sparkplug B 卡 (/ui/main) 每 5 秒调用一次。 - OPC UA 服务器 (
/ui/opcua/*) 进行内网轮询 (2 秒) — 仅使用OPCAndTagsCache.getInstance()+LastValueMap.getInstance(),负载较低。 - 旧版废弃 (deprecated):
/api/http(APIController) — 建议使用/api/v1/tag/{tagId}/value。
6. 代码位置
- 控制器:
src/plantpulse/app/edge/api/v1/—OpcAPI·TagAPI·AppAPI·SystemAPI·SystemTimeAPI·MonitoringAPI·EdgeStatusAPI·HaAPI·ConnectIoAPI·FormulaAPI - MCP 服务器:
src/plantpulse/app/edge/module/mcp/— MCP 服务器 - envelope · 通用响应:
api/v1/RestApiSupport.java·api/v1/dto/Api*.java - OPC UA 服务器:
src/plantpulse/app/edge/module/opcua/OPCUAViewerController.java - 升级代理:
src/plantpulse/app/edge/module/system/config/ConfigController.java - 业务逻辑:
src/plantpulse/app/edge/module/connect/ConnectService.java - 响应 envelope 助手:
plantpulse.app.edge.core.web.ControllerSupport(ok()/error())