跳到主要内容

3. 响应格式与错误处理

V5 API 的所有调用均返回 类型 DTO 或 boolean/long。失败时将返回如下安全默认值。

3.1 V5 响应模式​

方法模式成功返回失败返回
create(req) / update(id, req) / get(id)*ResponseV5 实例null
list() 系列List<*ResponseV5>空列表 (绝对非 null)
delete(id)truefalse
exists(id)true (存在)false (不存在或错误)
count() 系列long (≥0)0
Order 生命周期 (start/end/pause/resume/abort)true (成功)false (无法转移等)

不会抛出异常。 HTTP 错误、服务器 ERROR、JSON 解析失败将全部被上述表格中的安全默认值吸收。错误原因可通过调试日志或 envelope 的 _code/_message 确认。

3.2 内部 wire format (参考)​

V5 服务器以如下 envelope 形式响应。由于客户端库会自动解析,一般使用时无需关注。

// 단건 성공
{ "_status": "OK", "data": { ... } }

// 목록 성공
{ "_status": "OK", "data": [ { ... }, { ... } ] }

// 목록 — items 키 안에 배열
{ "_status": "OK", "data": { "items": [ ... ] } }

// 에러
{
"_status": "ERROR",
"_code": "E1002",
"_message": "validation failed: ...",
"_http_status": 400
}

3.3 V5 ErrorCode 映射​

当服务器发送 ERROR 响应时,_code 字段中包含以下代码。权威定义为产品的 plantpulse.core.exception.ErrorCode,且 HTTP 状态由该枚举值共同决定 —— 代码与状态为配对关系,仅根据其中之一进行分支处理即可。

代码HTTP名称常见原因
E1000400INVALID_REQUEST语法错误、类型不匹配
E1001400MISSING_PARAMETER缺失必要参数
E1002422VALIDATION_FAILED违反值范围·格式·域规则 (如 ID 前缀等)
E1100401UNAUTHORIZEDapi_key 缺失或无效
E1101429AUTH_TOO_MANY_ATTEMPTS同一 IP 认证失败累积 — 参见 Retry-After
E1102401TOKEN_EXPIRED令牌过期 — 可通过重新发行恢复
E1103401TOKEN_REVOKED令牌撤回 — 同一令牌无法恢复
E1104401TOKEN_IP_MISMATCH令牌绑定的 IP 与调用 IP 不一致
E1200403FORBIDDEN已认证但无权限
E1300404NOT_FOUND该 ID 不存在
E1400405METHOD_NOT_ALLOWED不允许的方法
E1500409CONFLICT资源冲突
E1501409DUPLICATE_ENTITY实体已存在
E1600413PAYLOAD_TOO_LARGE请求正文过大
E1700415UNSUPPORTED_MEDIA_TYPE不支持的媒体类型
E1800422IDEMPOTENCY_CONFLICT相同 Idempotency-Key + 不同 body
E1801409IDEMPOTENCY_IN_PROGRESS相同键处理中 — 稍后重试
E1900429RATE_LIMIT_EXCEEDED超过速率限制
E5000500INTERNAL_ERROR服务器内部错误
E5100500SERVICE_ERROR服务处理错误
E5200500DAO_ERROR数据访问错误
E5300500ENGINE_ERROR引擎处理错误
E5400502EXTERNAL_SERVICE_ERROR外部系统调用失败
E5500503SERVICE_UNAVAILABLE服务不可用 — 非判定结果,而是「无法执行」。 参见 Retry-After
E5503503SHUTTING_DOWN服务器关闭中 — 切换至其他节点或等待 Retry-After 时长
E5600504TIMEOUT服务器侧请求超时
存在两个 429 — 若混淆处理将无法恢复

E1900 (速率限制) 通过 降低速度 可解除。E1101 (认证尝试过多) 通过降低速度无法解除 — 凭据本身错误,且该 IP 将被锁定直至窗口关闭。若将两者绑定在相同的退避策略中,后者将陷入永久重试。

区分 400 与 422

400 表示请求无法被 «解析》,422 表示已解析但 «内容» 不符合规则 (自 2026-08-31 起)。通过状态码区分应修改格式还是修改数值 — 因为人类阅读消息,但代理(Agent)根据状态码决定下次尝试方案。

常见拒绝原因​

HTTPreason含义
422asset_statement.eql.missing_columns语句 EQL 未提供该种类的必要项 — 设备语句
422asset_statement.eql.unknown_type不存在该语句种类
422alarm.invalid.alarm_config_id创建报警设置时输入了自定义 ID。请通过 GET /alarm/config/new-id 接收
409—试图在同一标签上创建第二个频段报警设置。每个标签仅限一个

3.4 错误信息提取​

V5 服务方法不直接公开 envelope,因此可通过以下两种方式确认错误详情。

方法 1 — 调试模式​

APIClient_V5 client = new APIClient_V5(proto, host, port, user, token, true);
client.connect();

SiteResponseV5 result = client.site().create(req);
if (result == null) {
// 콘솔 로그에서 envelope 확인 가능
System.err.println("등록 실패 — 디버그 로그 확인");
}

控制台输出:

[V5] POST /api/v5/site body={...}
[V5] response: 400 {"_status":"ERROR","_code":"E1002","_message":"site_id must start with SITE_"}

方法 2 — 使用 BaseServiceV5 静态方法 (自定义扩展)​

BaseServiceV5 的辅助工具从 JSONObject 响应中提取错误信息。适用于在扩展库以创建捕捉 envelope 的自定义服务时。

import plantpulse.api.v5.service.BaseServiceV5;
import plantpulse.json.JSONObject;

// 환경 (envelope) JSONObject 가 있다면
String code = BaseServiceV5.getErrorCode(env); // 예: "E1002"
String message = BaseServiceV5.getErrorMessage(env); // 예: "validation failed: ..."
int httpStatus = BaseServiceV5.getHttpStatus(env); // 예: 400

3.5 安全调用模式​

CRUD 调用​

import plantpulse.api.v5.dto.request.SiteRequestV5;
import plantpulse.api.v5.dto.response.SiteResponseV5;

SiteRequestV5 req = new SiteRequestV5();
req.setSite_id("SITE_DJ");
req.setSite_name("대전공장");

SiteResponseV5 created = client.site().create(req);
if (created == null) {
// 실패 처리 — 예: 알람, 재시도, 다른 경로 시도
log.warn("사이트 생성 실패");
return;
}
System.out.println("등록 완료: " + created.getSite_id());

列表调用​

List<SiteResponseV5> sites = client.site().list();
// 실패해도 null 이 아님 — 그대로 순회 가능
for (SiteResponseV5 s : sites) {
System.out.println(s.getSite_id());
}
if (sites.isEmpty()) {
log.info("등록된 사이트 없음");
}

存在确认 → 查询​

if (client.site().exists("SITE_DJ")) {
SiteResponseV5 site = client.site().get("SITE_DJ");
// ...
}

Order 生命周期 (返回 boolean)​

String orderId = "ORDER_000001";

if (!client.order().start(orderId)) {
// 전이 불가 (예: 이미 START 상태)
log.warn("작업 시작 실패 — 현재 상태 확인");
return;
}

// 작업 진행 ...

if (!client.order().end(orderId)) {
log.warn("작업 종료 실패");
}

3.6 重试模式​

V5 不执行自动重试。如需应对临时性网络错误,请自行实现重试逻辑。

import java.util.function.Supplier;

public static <T> T callWithRetry(Supplier<T> apiCall, int maxRetries, long baseDelayMs) {
int attempt = 0;
while (true) {
T result = apiCall.get();
if (result != null) return result; // 성공
if (++attempt >= maxRetries) return null; // 포기
try {
Thread.sleep(baseDelayMs * attempt); // 지수 백오프
} catch (InterruptedException ie) {
Thread.currentThread().interrupt();
return null;
}
}
}

// 사용
SiteResponseV5 site = callWithRetry(
() -> client.site().get("SITE_DJ"),
3,
1_000L);

503 · 429 与 Retry-After​

因重启或过载导致当前无法接收请求时,会随附 Retry-After (秒级整数)。Java 客户端将遵守此标头,但默认等待时间不超过 30 秒。直接调用时请遵循以下规则:

场景响应
Web 层关闭 (重启 · 重新部署) — 代理响应503 E5500, Retry-After: 15
服务器关闭进行中503 E5503, Retry-After: 15
后端存储临时不可用 (DB 重启等)503 E5500, Retry-After: 15
每个令牌请求额度超限429 E1900, Retry-After 计算额度
流并发数超限429 E1900 或 503 E5500, Retry-After
  • 读取操作:等待相应时长后再次调用。
  • 写入操作:使用 相同 Idempotency-Key 再次调用。因重启而中断的请求可能已被执行。
  • 500 为故障,502 E5400 为外部调用失败。两者均无 Retry-After。

3.7 空响应与 null 响应区分​

在 V5 中,null 或 false 同时包含以下两种含义:

  1. 服务器无数据 (NOT_FOUND, E1300)
  2. 调用失败 (网络错误、验证失败等)

如需区分,请配合使用 exists()。

if (!client.site().exists("SITE_DJ")) {
log.info("사이트가 등록되어 있지 않습니다");
// 신규 등록 로직
} else {
SiteResponseV5 site = client.site().get("SITE_DJ");
if (site == null) {
log.error("사이트는 존재하지만 조회 실패 — 일시적 오류 가능");
}
}

下一步​