接口文档里最容易让人误判的词,往往就是“成功”。请求发出后返回 200,有时只说明HTTP请求处理正常;返回 202 Accepted,含义更明确:请求已经受理,但处理尚未完成。RFC 9110还特别说明,异步操作最终可能执行,也可能没有执行。
放到设备控制、订单处理、审批、文件生成这些场景里,道理是一样的。应用把“受理成功”直接显示成“操作成功”,现场设备没有动作,用户却已经离开页面;过一会儿失败回调到了,前台、后台和设备状态就出现了三个答案。

一条控制指令,至少要分清受理和执行
红数科技在接口评审时,会先挑一条有真实后果的指令往下追。比如开闸、启动设备、修改功率、取消订单或下发任务:谁可以发,目标对象是谁,什么状态下允许执行,参数范围是多少,发出以后由谁真正完成,最终结果从哪里回来。
这不是补一份接口名称清单。每条指令都应落到命令矩阵里,开发、设备或平台厂商、测试人员看到的是同一件事。
| 需要确认的内容 | 文档里至少要写到什么程度 |
|---|---|
| 命令身份 | 命令编码、名称、接口路径或Topic、请求方向、适用版本 |
| 操作对象 | 设备、订单、任务等对象的唯一编号,编号由哪一方生成 |
| 权限与前置条件 | 哪类账号可操作;目标处于什么状态、安全条件满足到什么程度才允许执行 |
| 参数 | 类型、必填项、单位、精度、范围、枚举、默认值和越界处理 |
| 命令编号 | command_id或等价字段由谁生成,重复提交时是否仍指向同一次操作 |
| 受理结果 | 已接收、校验不通过、无权限、状态不允许、系统繁忙分别怎样返回 |
| 执行过程 | 是否存在待下发、已下发、执行中等中间状态,在哪里查询 |
| 最终结果 | 完成、失败、取消、过期怎样判断;由回调、查询还是两者共同确认 |
| 超时与重试 | 多久算超时,谁重试,最多几次,同一指令重发会不会重复执行 |
| 取消与补偿 | 什么阶段还能取消;指令已经部分执行时,系统和现场怎样收口 |
| 审计 | 谁在什么时间对哪个对象下发了什么参数,原值、目标值和最终结果是否可查 |
这里有个必须问到底的问题:通信超时以后,调用方能不能直接再发一次?HTTP规范对幂等的定义,是同一请求执行多次与执行一次具有相同的预期效果,但一个具体的控制接口不会因为使用了HTTP就自动具备幂等性。开闸、扣款、出货、启动任务这类操作,需要项目自己定义幂等键、有效期和重复请求的返回方式。

远程控制还要保留设备自身的安全边界。软件可以提交任务和参数,不能借接口绕过急停、限位、互锁、人员检测或设备保护。设备因条件不满足而拒绝执行,应返回明确原因;不能把安全拒绝包装成网络异常,再让上层自动重试。
回调确认的是状态变化,不是“对方又发来一份数据”
回调接口经常只写URL、请求示例和一个“成功返回”。这只能让第一次正常推送跑通,支撑不了生产环境。网络会中断,接收方会超时,发送方会重试;同一个事件可能重复到达,不同事件也未必按发生顺序到达。
这些不是理论上的边角情况。Stripe的官方文档明确提醒,事件可能重复,且不保证投递顺序;GitHub的Webhook最佳实践要求使用密钥验证来源、启用HTTPS、尽快返回成功响应,并用每次投递的唯一标识防范重放。不同平台的具体超时和重试次数并不相同,所以项目不能把某一家平台的参数当成通用规则,必须向当前接口提供方逐项确认。
一份能用于开发的回调规则表,通常要回答下面这些问题:
| 项目 | 需要共同确认的规则 |
|---|---|
| 事件范围 | 会推送哪些事件;创建、状态变化、完成、失败、取消是否各有明确事件类型 |
| 事件身份 | event_id、command_id、业务对象编号分别是什么,三者怎样关联 |
| 地址与环境 | 测试、预发布、生产回调地址是否分开,地址变更由谁审核和生效 |
| 来源验证 | HTTPS、签名字段、签名算法、时间戳、密钥轮换和重放时间窗怎样处理 |
| 报文 | 字段类型、必填与可空、枚举、时间格式、时区、样例和版本号 |
| 接收确认 | 接收方返回什么状态才算成功;是收到即应答,还是业务处理完再应答 |
| 超时与重试 | 单次等待多久,哪些结果会重试,退避间隔、最大次数和最终失败怎样通知 |
| 重复与乱序 | 怎样去重;旧状态晚到时是否忽略;同一对象是否保证局部顺序 |
| 补拉与重放 | 回调丢失后能否按事件编号、时间段或业务对象查询并重新投递 |
| 日志保留 | 双方保留原始请求、响应、投递次数、时间和签名校验结果多久 |
接收方最好先完成签名、时间窗、事件身份和基本格式校验,把事件可靠写入队列或本地存储,再快速返回约定的成功响应。真正耗时的业务处理放到后面。这样做不是为了追求架构复杂,而是避免对方因为等待超时持续重试,最终把一次正常事件变成一串重复任务。

回调与查询接口还要能互相校验。某次回调漏了,系统可以按 command_id 查询最终状态;查询显示已完成,迟到的“执行中”回调不能把状态改回去;失败事件重新投递,也不应重复退款、重复发货或重复生成任务。这里真正需要锁定的是状态变化规则,而不是只保证每个接口单独有响应。
错误码要告诉系统下一步怎么办
只给 0=成功,1=失败,或者每个接口都返回一段不同格式的错误文字,联调时都不够用。错误码的价值不是证明系统报过错,而是让调用方判断问题在哪一层、能否重试、是否需要改参数、用户应看到什么,以及哪一方负责处理。
先把错误分层,很多争议会少一半。
| 错误所在层 | 常见情况 | 调用方通常要做的事 |
|---|---|---|
| 连接与协议 | DNS失败、连接超时、TLS失败、报文无法解析 | 保留原始异常,按约定重试或告警,不假定业务未执行 |
| 身份与权限 | 令牌失效、签名错误、账号无权操作目标对象 | 刷新凭证或终止请求;权限问题不能无限重试 |
| 请求校验 | 缺少必填字段、类型错误、数值越界、版本不支持 | 指出具体字段,由调用方修正后重新提交 |
| 业务状态 | 订单已关闭、任务不可取消、设备当前状态不允许 | 刷新对象状态,按业务规则提示或转人工 |
| 设备与现场 | 离线、故障、安全联锁未满足、动作超时 | 保留设备原因,等待现场条件变化或人工处理 |
| 平台内部 | 服务繁忙、依赖服务异常、未知错误 | 使用可追踪编号记录,按明确的可重试标志处理 |
HTTP状态码、MQTT原因码、设备故障码和业务错误码不是一回事,不能塞进同一列互相替代。HTTP层可以说明请求在协议层的处理结果,业务响应仍要给出稳定的错误编号。设备厂家已有故障码时,平台可以做映射和解释,但应保留原始码,避免现场人员拿着厂家手册却找不到对应记录。
错误响应建议保持固定结构,至少包含稳定的 code、可读的 message、是否允许重试、出错字段、trace_id或等价追踪编号、发生时间和必要的原始详情。面向HTTP API时,可以参考RFC 9457定义的Problem Details组织机器可读的错误信息。它解决的是错误响应的通用表达,不会替项目定义设备故障含义和业务处置规则。

错误文字也要分清给谁看。开发日志可以写接口、节点和原始原因;操作后台要告诉运维人员该检查哪台设备、哪个条件;普通用户只需要知道当前操作有没有完成、是否可以重试,以及必要时等待什么。把数据库异常、内部地址或密钥信息直接显示到前台既没有帮助,也会增加安全风险。
三张表最终要落到同一套状态上
命令矩阵、回调规则表和错误码表分别确认后,还要放回同一条指令里核对。比较常见的一套状态会经过“已创建、已受理、待下发、执行中”,最后进入“已完成、已失败、已取消或已过期”。具体项目不一定需要这么多状态,但每个状态从哪里来、谁有权改变、能否回退,必须唯一。
真正的联调记录不应只截一张“调用成功”的页面。至少要把这些信息放在一起:
- 一次请求的原始报文、响应、
command_id和调用时间; - 平台受理、下发和设备回执的时间;
- 回调的
event_id、投递次数、每次响应和签名校验结果; - 最终业务状态、设备实际动作与操作日志;
- 如果失败,使用了哪个协议状态、业务错误码和设备原始故障码。
这样才能回答一个经常被忽略的问题:调用方没收到响应时,这条指令究竟没有到达、已经受理但还没执行,还是已经执行成功只是响应丢了。三种情况的处理完全不同,不能都归为“超时后重试”。
用最高风险的一条指令做确认
纸面评审结束后,不必一开始就把所有接口都跑一遍。先选项目里后果最重、链路最长的一条指令,用真实设备、测试环境或双方认可的模拟器走完整过程。正常执行只是第一轮,随后故意制造参数越界、无权限、目标状态不允许、设备离线、响应超时、重复提交、重复回调、回调乱序和处理中取消。
每个场景都要提前写出预期:接口返回什么,设备应不应该动作,状态怎样变化,是否重试,前台显示什么,日志由哪一方提供。实际结果与预期不一致时,修改文档、实现或测试用例,不能只在群里留一句解释。
对外提供HTTP接口,可以用机器可读的OpenAPI文档统一请求、响应、鉴权、回调和错误示例。资料核对时,OpenAPI官方最新发布版本为3.2.0。如果项目以MQTT、消息队列或事件总线承载指令和事件,AsyncAPI当前规范为3.1.0,可用来描述通道、消息、发送与接收操作。项目不必为了追新强行升级版本,重点是选定版本后,文档、示例、模拟环境和实际报文保持一致。
什么状态才算确认完成
双方最终应留下三张经过评审的表、完整请求与回调样例、一套可重复执行的异常测试、未解决问题清单,以及带日期的版本基线。每个结论要能找到确认人和证据。口头说“按常规处理”,不能算规则;测试环境偶尔跑通一次,也不能代表已经具备上线条件。
红数科技通常把这轮确认放在正式排期前。到了这一步,一条指令无论完成、失败,还是已经执行但响应丢失,双方都能沿着同一个command_id查到设备动作、回调记录和原始错误;换人接手,也不用重新翻聊天记录。做到这个程度,接口才不只是“调得通”,而是真正具备交付条件。