跳到主要内容

3. 响应格式与错误处理

V5 API 的所有调用都返回类型化 DTOboolean/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 字段中会包含以下代码。

代码含义常见原因
E1001INVALID_INPUT缺少必填字段、类型不匹配
E1002VALIDATION_FAILED违反 ID 前缀规则(如 SITE_
E1100UNAUTHORIZEDapi_key 缺失或无效
E1101FORBIDDEN权限不足
E1200CONFLICTID 重复、外键冲突
E1300NOT_FOUND不存在该 ID
E1400DOMAIN_RULE_VIOLATIONOrder 状态无法转换等
E1500DEPENDENCY_FAILED外部系统响应失败
E1900INTERNAL_ERROR服务器内部错误

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 static 方法(自定义扩展)

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 = "ORD_20260514_001";

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

3.7 区分空响应与 null 响应

在 V5 中,nullfalse 同时包含以下两种含义:

  1. 服务器上没有数据NOT_FOUNDE1300
  2. 调用失败(网络错误、校验失败等)

若要区分两者,可配合使用 exists()

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

后续步骤