跳到主要内容

REST API (v1)

PlantPulse Edge 的 v1 REST API 手册。这是用于界面/外部系统处理 OPC、标签、监控、外部传输健康检查的标准接口。

项目值
Base URLhttp(s)://<edge-host>/api/v1
Content-Typeapplication/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 路径也相同
HTTPSedge.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...
字段含义
methodHTTP 方法
pathrequest URI (不含查询参数)
query查询字符串 — 被拒绝的 ?api_key=... 部分通过 *** 掩码处理
statusHTTP 响应代码
duration处理时间 (ms)
ip默认为 RemoteAddr。仅在部署了 edge.rest.api.trust_x_forwarded_for=true 的 reverse proxy 时为 X-Forwarded-For 第一个 IP
usersession 用户则为 user_id / apikey 则为 XXXX*** (len=16) (前 4 位 + 长度) / 匿名则为 -
authsession (浏览器登录) / apikey (X-API-Key/Bearer) / deprecated_query (被拒绝的 ?api_key= 尝试) / none (缺失密钥或匿名)
uaUser-Agent (超过 80 个字符时 … 截断)
error发生异常时追加 error=ClassName

日志级别分级​

状态级别意图
5xx 或 throwableERROR服务器错误 — 立即报警
401, 403WARN认证/权限拒绝 — 优先 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 分页​

列表端点的通用查询参数。

查询默认值说明
page0从 0 开始计算的页码
size0若为 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​

MethodURL说明响应
GET/api/v1/opcOPC 列表 + 运行时状态 + 60 分钟趋势200 · 400
POST/api/v1/opcOPC 注册。可通过 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​

MethodURL说明响应
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) · limit(默认 600, 15000 — 超过则等间隔抽样提供)。

写入失败的代码映射 — 标签缺失 404 TAG_NOT_FOUND · 连接中断 503 DISCONNECTED · 采集停止 503 OPC_STOPPED · 只读标签 400 RW_VIOLATION · 类型不匹配 400 TYPE_MISMATCH · 其他 500 INTERNAL_ERROR。

2.3 应用 (Docker 容器) — 9​

MethodURL说明响应
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}/statsCPU · 内存 · 网络 · 磁盘快照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​

MethodURL说明认证
GET/api/v1/system/healthliveness + 8 种组件检查不必要
GET/api/v1/system/readyreadiness (监控 + 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/eventsSSE 流 (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/reboot5 秒后主机重启强制
POST/api/v1/system/restart3 秒后 Edge Agent 重启强制
reboot · restart 无法«关闭»认证

即使通过 edge.rest.api.auth=false 关闭了认证,这两项仍要求会话登录或 API 密钥。因为如果任何人都能调用,其本身将导致服务拒绝。

2.5 监控 · 边缘 — 4​

MethodURL说明
GET/api/v1/monitoring运行时间 · OPC 数量 · 注册标签 · 扫描速度
GET/api/v1/edgeedge_id · site_id · 版本 · 主机名 · 模式
GET/api/v1/edge/status边缘状态摘要
GET/api/v1/transfer各个传出传输 (MQTT · SparkplugB · 平台 API) 状态

若 monitoring 在启动后监控尚未就绪,则返回 503 MONITOR_NOT_READY — 这并非故障,而是「尚未就绪」。

2.6 冗余 (HA) — 4​

MethodURL说明
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 被拒绝」,请首先检查是否为启动初期。缓存填充后将自动恢复 — 无需修改配置。

🔴 此 API 不执行«转移» — 而是提升与降低两次独立调用

此调用仅更改自身网关。不对对端进行询问、锁定或接管。不存在协调代码。

因此,实际上可能出现两个网关同时持有权限的状态。请遵守以下顺序:

  1. 降低 A 的权限 — PUT /api/v1/ha/write-authority {"enabled":false}
  2. 确认 A 为 false — GET /api/v1/ha/status
  3. 提升 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 模式。若自动故障转移未运行,则返回 409 NOT_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​

MethodURL说明
POST/api/v1/connect/backup将所有 OPC + 所有标签导出为一个 JSON
POST/api/v1/connect/restore将该 JSON 原样应用 (upsert)

对称操作 — 将 backup 响应原样发送至 restore 正文即可。请务必检查 restore 响应的 skipped[] : 即使个别行失败,整体不会失败,而是跳过并仅递减计数。

2.8 计算公式 — 1​

MethodURL说明
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_corereadiness — 采集器/监控是否启动。启动竞态期间为 DOWN,导致 health 为 503。与平台连接无关,在 STANDALONE 网关中亦有意义
opc_ua端口并非固定。遵循 app.properties 的 opc.ua.server.tcp.port (开发网关 4840, 默认 12000)。曾因硬编码为 12000 导致开发网关永久报告为 DOWN
grafana2026-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[],通过一个键即可区分。

codeHTTP含义
MISSING_BODY400请求正文为空
MISSING_FIELD400缺失必需字段
INVALID_JSON400JSON 解析失败
INVALID_RANGE400page·size·from~to 范围违规
INVALID_ARGUMENT400一般参数错误 (镜像·卷白名单等)
INVALID_NAME400容器名称包含注入字符
VALIDATION_FAILED400多个字段同时出错 — 请查阅 field_errors[]
TOO_MANY400批量请求上限 (100 条) 超出
RW_VIOLATION · TYPE_MISMATCH400标签写入失败 — 只读标签 / 类型不匹配
UNAUTHORIZED401认证缺失或无效
HTTPS_REQUIRED403使用了明文 HTTP 访问 — 并非密钥问题
OPC_NOT_FOUND · TAG_NOT_FOUND · APP_NOT_FOUND404目标不存在
CONTAINER_EXISTS409同名容器已存在
PRECONDITION_FAILED412If-Match 的 ETag 不匹配 — 期间已被他人修改
START_FAILED · STOP_FAILED422OPC 采集控制失败
DOCKER_PULL_FAILED · DOCKER_RUN_FAILED502Docker 命令执行失败
MONITOR_NOT_READY · NOT_READY · DISCONNECTED · OPC_STOPPED503尚未就绪/已断开 — 并非请求错误
MISCONFIGURED500edge.rest.api.key 为空
INTERNAL_ERROR · *_FAILED500其他内部错误
请勿将 503 视为 400

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())