跳到主要内容

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 路径同样适用
HTTPS默认 edge.rest.api.https_only=true。plain HTTP client 调用会被拒绝,或遵循 Tomcat HTTPS redirect 策略

认证 (ApiAuthFilter)

app.properties 中通过 edge.rest.api.auth=true 启用。密钥为 edge.rest.api.key,运行环境中请通过 EDGE_REST_API_KEY_FILEEDGE_REST_API_KEY 注入。

以下 3 种方式满足任意一种即可通过:

A. 登录会话通过(浏览器)

已通过 /login/form 登录的用户 — 若会话中存在 _USER_LOGIN attribute (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()(防御 timing-attack)。

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)INFO日常调用

应用

  • 谁发送了错误的密钥 — 用 grep '\[API_ACCESS\].*status=401' 确认 401 追踪
  • 5xx 回归追踪grep '\[API_ACCESS\].*status=5' + error= 字段
  • 调用频率 / 按用户聚合 — 按 auth=apikey/session 分组,按 ip 分析
  • 响应延迟慢查询 — 提取 duration= 较大的行

密钥明文在任何地方都不会写入日志 — 仅凭 query 掩码 + user 字段前 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. 端点一览

1.1 OPC

slugMethodURL说明
flow_edge_opc_listGET/api/v1/opcOPC 列表(含 tag_count / connection_status / scan_status)
flow_edge_opc_createPOST/api/v1/opcOPC + 标签成组注册
flow_edge_opc_updatePUT/api/v1/opc/{opcId}OPC + 标签成组更新(path opcId 覆盖 body opc_id)
flow_edge_opc_deleteDELETE/api/v1/opc/{opcId}批量删除 OPC 及其下级标签
flow_edge_opc_startPOST/api/v1/opc/{opcId}/start开始采集
flow_edge_opc_stopPOST/api/v1/opc/{opcId}/stop停止采集

1.2 Tag

slugMethodURL说明
flow_edge_tag_listGET/api/v1/opc/{opcId}/tag特定 OPC 的标签列表(含最后值/状态)
flow_edge_tag_createPOST/api/v1/opc/{opcId}/tag单条标签注册
flow_edge_tag_updatePUT/api/v1/tag/{tagId}单条标签部分更新(与既有 row 合并后 upsert)
flow_edge_tag_deleteDELETE/api/v1/tag/{tagId}单条标签删除
flow_edge_tag_readGET/api/v1/tag/{tagId}/value[?fresh=true]缓存值(default),若为 fresh=true 则直接读取 PLC
flow_edge_tag_writePOST/api/v1/tag/{tagId}/value写入值 — 支持所有协议(HTTP / OPC-UA / Modbus / MELSEC / S7 / LS / EIP)

1.3 Monitoring / Edge / Transfer

slugMethodURL说明
flow_edge_monitoringGET/api/v1/monitoringCPU/内存/网络/磁盘/线程等 MonitorBean 的所有字段
flow_edge_infoGET/api/v1/edge边缘标识/版本/站点/OS/运行时间元信息
flow_edge_transferGET/api/v1/transfer各 outbound transfer (API/MQTT/Sparkplug) 的健康状态

1.3a System(无需认证 — 供 LB / k8s probe / OTA 使用)

/api/v1/system/* 中以下 3 个通过 ApiAuthFilter 白名单(不暴露任何 OPC/Tag 信息)。

MethodURL说明HTTP
GET/api/v1/system/health8 个组件 (cassandra / redis / mqtt / node_red / opc_ua / edge_core / tse / grafana) TCP 200ms probe + 综合 status200 (all UP) / 503 (DEGRADED)
GET/api/v1/system/readymonitor + V5 api_client 就绪状态200 / 503
GET/api/v1/system/versionMetadata.VERSION + BUILD_DATE + /etc/kopens/version.env image_tag + /.dockerenv container_mode200

/health 示例:

{
"data": {
"status": "UP",
"uptime_ms": 1720008,
"components": {
"cassandra": "UP", "redis": "UP", "mqtt": "UP",
"node_red": "UP", "opc_ua": "UP"
}
}
}

/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/api/health(300 秒)probe 判断 auto-rollback。Docker HEALTHCHECK 也使用 /health

1.4 OPC-UA 查看器 (OPCUAViewerAPI)

内置 OPC-UA 服务器的 endpoint / 节点树 / 时序 — 供 /ui/opcua 画面使用。

MethodURL说明
GET/ui/opcua/info[?reveal=true]内置 OPC-UA 服务器的 endpoint URL (TCP/TLS) / Application 名称 / 认证信息 (reveal=true + 认证会话时的 password 明文)
GET/ui/opcua/treeSite → OPC → Tag 树(含 NodeId)。EDGE_* 系统 OPC 始终为 connection_status=CONNECTED
GET/ui/opcua/history?tagId=...&minutes=N&limit=M查询 Cassandra tm_tag_point 的时序。输入校验后返回按 ASC 排序的 points 数组

1.5 升级 / 系统 (ConfigAPI)

/config/* 与 v1 REST API 相互独立(用于管理员画面),但在本手册中一并整理。

MethodURL说明
GET/config/upgrade/check以 server-to-server 方式取得 product.kopens.io 的 VERSION.JSON 并返回 {result, latest_version, latest_build_date}。失败时返回 result=ERROR
POST/config/upgrade执行 bin/upgrade.sh(长时间作业)
POST/config/restart执行 bin/restart.sh(Tomcat 重启)
POST/config/reboot执行 bin/reboot.sh(OS 重启)
POST/config/temp-clean执行 bin/clean.sh(清理日志/临时文件)
POST/config/backup执行 bin/backup.sh
POST/config/firmware执行 bin/firmware.sh (dnf update -y)
GET/config/load返回 app.properties 文本
POST/config/save保存 app.properties

2. 请求 / 响应示例

2.1 GET /api/v1/opc — OPC 列表

curl -s http://<edge-host>/api/v1/opc | jq
{
"result": "OK",
"data": {
"data": [
{
"opc_id": "OPC_UA_Kepware",
"opc_type": "OPCUA",
"opc_name": "OPC_UA_Kepware",
"opc_agent_ip": "192.168.0.40",
"opc_agent_port": "49320",
"auto_collect": "true",
"timecycle": "1000",
"options": {"username": "kopens", "password": "***", "discovery": "false"},
"tag_count": 11,
"point_count": 5819,
"connection_status": "CONNECTED",
"scan_status": "START"
}
]
}
}

⚠ 为 data.data[] 形式(双层 wrap)— 参见上文 envelope 章节。

2.2 POST /api/v1/opc — OPC + 标签成组注册

curl -X POST http://<edge-host>/api/v1/opc \
-H "Content-Type: application/json" \
-d '{
"opc_id": "OPC_NEW",
"opc_type": "OPCUA",
"opc_name": "신규 연결",
"opc_agent_ip": "10.0.0.10",
"opc_agent_port": "49320",
"site_id": "SITE_00001",
"auto_collect": true,
"timecycle": 1000,
"tag_list": [
{"tag_id": "TAG_001", "tag_name": "Sine",
"plc_address": "ns=2;s=Sine1", "data_type": "Float"}
]
}'

2.3 POST /api/v1/opc/{opcId}/tag — 单条标签注册

{
"tag_id": "TAG_NEW",
"tag_name": "신규 태그",
"plc_address": "ns=2;s=NewTag",
"data_type": "Float",
"description": "..."
}

响应:

{ "result": "OK", "data": { "result": "SUCCESS", "tag_id": "TAG_NEW" } }

site_id 由 OPC 自动填充。

2.4 PUT /api/v1/tag/{tagId} — 标签部分更新

请求 body 只发送需要变更的字段。服务器端与既有 row 合并后 upsert。

{ "description": "변경된 설명" }

2.5 GET /api/v1/tag/{tagId}/value — 查询最后值

默认(缓存):

curl -s http://<edge-host>/api/v1/tag/TAG_UA_0004/value | jq
{
"result": "OK",
"data": {
"tag_id": "TAG_UA_0004",
"value": "28.9688",
"value_time": "2026-05-06 20:59:39.000",
"value_read_status": "SUCCESS",
"value_read_error_message": ""
}
}

fresh=true(直接读取 PLC):

curl -s 'http://<edge-host>/api/v1/tag/TAG_UA_0004/value?fresh=true' | jq

PLC read 失败时,外部 result=ERROR + message:

{ "result": "ERROR", "message": "fresh read failed: ..." }

⚠ 即使查询不存在的标签,也会以 envelope result=OK 响应,并返回 value 被填充为 "-" 的 placeholder 载荷(当前行为)。

2.6 POST /api/v1/tag/{tagId}/value — 写入值

curl -X POST http://<edge-host>/api/v1/tag/TAG_HTTP_001/value \
-H "Content-Type: application/json" \
-d '{ "value": "42" }'

成功:

{
"result": "OK",
"data": {
"result": "SUCCESS",
"opc_id": "OPC_LS_TEST",
"plc_address": "D1000",
"value": "42",
"actual_read": "42"
}
}

失败(驱动返回 false / OPC DISCONNECTED / 调度器未运行)— 外部 envelope result=ERROR:

{ "result": "ERROR", "message": "쓰기 실패 (opc_type=EIP) — EIP 펌웨어/패치에 따라 ..." }

支持协议:HTTP / OPC-UA / Modbus / MELSEC / S7 / LS XGT / EIP — 均可。EIP 可能因 PLC 固件/补丁版本而存在限制。

2.7 POST /api/v1/opc/{opcId}/start — 开始采集

curl -X POST http://<edge-host>/api/v1/opc/OPC_NEW/start

不支持内置 OPCUA/MODBUS 模拟器的自动启动。测试/演示数据请使用外部 plantpulse-simulator 或测试标签。

2.8 GET /api/v1/edge — 边缘标识 / 运行信息

EdgeAPI.edgeInfo() 直接填充的字段:

{
"result": "OK",
"data": {
"id": "EDGE_00303",
"site_id": "SITE_00001",
"site_name": "S1_LOTTE_CS_DJ_SITE",
"hostname": "EDGE-303",
"product_name": "PlantPulse Edge",
"version": "2026",
"build_date": "2026-05-08",
"os_name": "Linux",
"os_version": "6.14.5-100.fc40.x86_64",
"os_arch": "amd64",
"started": true,
"started_date": 1778066700000,
"uptime_ms": 124500,
"always_on": false,
"ttl": 30,
"sended_count": 8063
}
}

2.9 GET /api/v1/monitoring — 系统指标

原样序列化 MonitorBean 的所有字段。1 秒刷新。

curl -s http://<edge-host>/api/v1/monitoring | jq '.data | keys'

代表性字段:cpu_used_percent, memory_used_percent, disk_used_percent, temperature, ping, api, opc_count, tag_count, mps, mps_history, queue_size, plc_value_read_success_count, plc_value_read_error_count, plc_value_write_success_count, plc_value_write_error_count, plc_con_connected_count, plc_con_disconnected_count, plc_scan_start_count, plc_scan_not_collect_count, plc_scan_stop_count, sended_point_count, sended_point_bytes, system_total_db_size, system_error_count, docker_on, docker_container_up_count, docker_container_total_count

2.10 GET /api/v1/transfer — 外部传输健康

curl -s http://<edge-host>/api/v1/transfer | jq
{
"result": "OK",
"data": {
"transfers": [
{ "type": "API", "enabled": true, "connected": true, "sent_count": 8063 },
{ "type": "MQTT", "enabled": true, "connected": true, "sent_count": 12345 },
{ "type": "Sparkplug", "enabled": true, "connected": true,
"group_id": "Plant1", "edge_node_id": "EDGE_00303",
"bdSeq": 7, "seq": 211 }
]
}
}

将各 transfer 的 getStatus() 结果原样以数组返回。调用中发生异常时回退为 {type, status_error}

2.11 GET /ui/opcua/info — 内置 OPC-UA 服务器信息

curl -s http://<edge-host>/ui/opcua/info | jq
{
"result": "OK",
"data": {
"application_name": "PlantPulse Edge OPC-UA Server",
"product_uri": "urn:plantpulse:opcua:server",
"domain": "127.0.0.1",
"tcp_endpoint": "opc.tcp://127.0.0.1:12000",
"tls_endpoint": "opc.tcp://127.0.0.1:12443",
"namespace_index": 2,
"auth_username": "edge",
"auth_password_set": true,
"auth_anonymous": false
}
}

若为 ?reveal=true 且为已认证会话,则追加 auth_password 明文。

2.12 GET /ui/opcua/tree — 节点树

Site → OPC → Tag 三层树。每个标签包含 NodeId、最新值、数据类型、description。EDGE 系统 OPC 强制为 connection_status=CONNECTED

NodeId 约定:ns=2;s=<SITE>.<OPC>.<TAG>

2.13 GET /ui/opcua/history — 时序

参数默认限制
tagId必填最大 200 字符
minutes101 – 1440 (24h)
limit6001 – 5000
curl -s 'http://<edge-host>/ui/opcua/history?tagId=TAG_S7_1000&minutes=10&limit=100' | jq
{
"result": "OK",
"data": {
"tag_id": "TAG_S7_1000",
"minutes": 10,
"count": 100,
"points": [
{ "ts": 1778176880010, "value": "211", "type": "integer", "quality": 192 }
]
}
}

tagId 缺失/空白/超过 200 字符时返回外部 result=ERROR (message)。即使 Cassandra timestamp 以 wrap 对象返回,也会通过 extractTimestampMs 转换为 epoch ms。


3. 错误响应模式

场景响应形式
外部 envelope ERROR(write 失败 / fresh read 失败 / opcua/history 输入校验失败 / upgrade/check 下载失败){result:"ERROR", message:"..."} (HTTP 200)
内部业务 ERROR(CRUD 时 tag/opc not found / DB 完整性 / 调度器未运行){result:"OK", data:{result:"ERROR", msg:"..."}} (HTTP 200)
Spring 未映射(必填参数缺失、方法错误等)HTTP 500 + 堆栈跟踪 — 建议:在控制器层进行 @RequestParam(required=false) 处理后转换为 envelope
认证 — edge.rest.api.auth 的安装默认值为 true,且 ApiAuthFilter 要求 X-API-Key / Bearer(参见上文认证章节)。仅健康端点例外

4. 运行备注

  • 标签单条查询:APP_TAG.xmlR03 (WHERE TAG_ID = :tag_id ALLOW FILTERING)。
  • OPC + 标签成组注册 / 单条标签注册后调用 ConnectService.restartComponents()OPCAndTagsCache reload 并重启 OPC-UA 服务器。
  • 写入值 (tagWriteByTagId):HTTPHTTPDriver.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

5. 代码位置

  • 控制器:src/plantpulse/app/edge/api/v1/OpcAPI.java · api/v1/TagAPI.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 helper:plantpulse.app.edge.core.web.ControllerSupport (ok() / error())