# 第三方入仓测量接口联调说明 > 版本:2026-08-04 > 网关基地址:`http://127.0.0.1:9000`(测试环境替换为实际网关域名) ## 一、接口用途 第三方测量设备每次回传一条箱规测量数据和一张对应图片。后端按入仓码匹配入库单,并校验指定货物明细属于该入库单关联订单,然后形成一条测量明细。 接口只保存测量结果,不执行客服审核、计费、托盘拆分、库位占用或完成入库。 ## 二、提交接口 ```http POST /openapi/v1/inbound-measurements Content-Type: multipart/form-data ``` 请求头: ```http X-App-Key: <第三方应用Key> X-Timestamp: X-Nonce: <本次请求唯一随机串> X-Signature: Idempotency-Key: <本次业务操作幂等键> ``` multipart 内容: | part 名称 | 类型 | 必填 | 说明 | |---|---|---|---| | `data` | UTF-8 JSON 文本 | 是 | 一条箱规测量数据,服务端使用原文验签 | | `image` | 二进制文件 | 是 | 该条明细唯一对应图片;JPG、PNG、BMP,最大 5 MB | `data` 示例: ```json { "eventNo": "M202608040001", "externalDetailNo": "BOX-20260804-0001", "warehouseCode": "CN-YW-01", "inboundCode": "ZBYHK2604100412", "inboundDetailId": "190000000000000001", "deviceCode": "DEVICE-01", "measuredAt": "2026-08-04T10:30:00+08:00", "boxSpecification": "40*30*47cm", "packageCount": 50, "lengthCm": 40.000, "widthCm": 30.000, "heightCm": 47.000, "unitWeightKg": 20.000, "remark": "" } ``` 字段说明: | 字段 | 必填 | 规则 | |---|---|---| | `eventNo` | 是 | 同一 AppKey 下唯一;1~128 位字母、数字、点、下划线、冒号或横线 | | `externalDetailNo` | 是 | 第三方物理测量明细唯一号;重复绑定会被拒绝 | | `warehouseCode` | 是 | 必须属于该 AppKey 预先绑定的仓库 | | `inboundCode` | 是 | 柏悦入仓码,后端据此匹配入库单 | | `inboundDetailId` | 是 | 柏悦提供的订单货物明细 ID;必须属于该入库单关联订单 | | `deviceCode` | 是 | 测量设备编码,最长 64 位 | | `measuredAt` | 是 | ISO 8601 时间且必须包含时区 | | `boxSpecification` | 是 | 箱规显示文本,最长 64 位 | | `packageCount` | 是 | 本箱规箱数,1~1,000,000 | | `lengthCm` | 是 | 单箱长度,单位厘米且大于 0 | | `widthCm` | 是 | 单箱宽度,单位厘米且大于 0 | | `heightCm` | 是 | 单箱高度,单位厘米且大于 0 | | `unitWeightKg` | 否 | 单箱重量,单位 KG且大于 0;重量 OCR 流程可另行确认 | | `remark` | 否 | 最长 500 字符 | ## 三、签名算法 先计算图片内容摘要: ```text imageSha256 = SHA256(image原始字节).lowerHex ``` 构造签名 body,`LF` 是单个换行符 `\n`: ```text signatureBody = data原始UTF-8文本 + LF + imageSha256 bodySha256 = SHA256(signatureBody的UTF-8字节).lowerHex ``` 构造六行 canonical string,末尾不追加换行: ```text POST /openapi/v1/inbound-measurements ``` 最终签名: ```text X-Signature = HMAC-SHA256(appSecret, canonicalString).lowerHex ``` `data` 中任何空格、换行或字段顺序变化都会改变签名。计算签名后必须发送完全相同的 `data` 原文和图片字节。 ## 四、成功响应 ```json { "code": "200", "message": "success", "data": { "eventNo": "M202608040001", "inboundCode": "ZBYHK2604100412", "inboundDetailId": "190000000000000001", "measurementId": "190000000000000010", "measurementItemId": "190000000000000020", "imageFileId": "190000000000000030", "accepted": true, "status": "RECEIVED", "receivedAt": "2026-08-04T02:30:00Z" }, "requestId": "...", "traceId": "...", "timestamp": "2026-08-04T02:30:01Z" } ``` 所有 ID 都是字符串,调用方不得转换为 JavaScript Number。 ## 五、事件查询 ```http GET /openapi/v1/inbound-measurements/events/{eventNo} ``` GET 同样需要 `X-App-Key`、`X-Timestamp`、`X-Nonce` 和 `X-Signature`,不需要 `Idempotency-Key`。canonical string 中幂等键行为空字符串,body 摘要使用空字节 SHA-256。 ## 六、幂等与重复规则 - `X-Nonce`:短时间防重放,使用后不能再次使用。 - `Idempotency-Key`:一次 HTTP 写操作标识;网络重试必须复用原值。 - `appKey + eventNo`:第三方业务事件唯一标识。 - `tenantId + inboundDetailId + externalDetailNo`:测量明细绑定唯一约束。 - 相同事件号或幂等键重试时,`data` 原文及图片内容必须与首次完全一致,否则返回冲突。 ## 七、主要失败场景 | 业务码 | 场景 | |---|---| | `A0400` | data 不是有效 JSON、图片为空或无法读取 | | `A0401` | AppKey、时间戳或签名无效 | | `A0403` | AppKey 未绑定请求仓库,或内部凭证无效 | | `A0404` | 入仓码不存在,或货物明细不属于入库单关联订单 | | `A0409` | 入库状态不允许测量、Nonce 重放、事件/幂等内容不一致、明细重复绑定 | | `A0422` | 字段缺失、格式或数值范围不符合要求 | | `S0503` | Order 或 File 服务暂不可用 | 普通业务失败应以响应体 `code` 判断,不能只判断 HTTP 状态。 ## 八、后台处理过程 ```text 验签和防重放 → 按入仓码及绑定仓库查入库单 → 调用 Order 校验货物明细归属 → 检查第三方明细号是否已经绑定 → 调用 File 保存并校验真实图片 → 写入一条测量批次和一条测量明细 → 返回测量、明细和图片ID ``` 如果图片已保存但测量数据落库失败,Firstleg 会调用 File 执行补偿清理。 ## 九、上线前置 部署新版 Firstleg 前,先执行: ```text firstleg/src/main/resources/db/phase2-third-party-single-measurement-migration.sql ``` 或执行已内联该增量的入库测量与计费统一脚本: ```text deploy/sql/2026-08-04-入库新增表字段-MySQL8一键执行.sql ``` 同时确认 Firstleg、Order、File 使用一致的 `INTERNAL_SERVICE_TOKEN`,并配置 `ORDER_SERVICE_URL`、`FILE_SERVICE_URL` 或启用 Nacos 服务发现。