docs: organize contracts by business domain

This commit is contained in:
2026-09-04 16:44:08 +08:00
parent 703f869841
commit 40492cdae8
79 changed files with 916 additions and 852 deletions
@@ -0,0 +1,193 @@
# 第三方入仓测量接口联调说明
> 版本: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: <Unix毫秒时间戳>
X-Nonce: <本次请求唯一随机串>
X-Signature: <HMAC-SHA256签名>
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
<X-Timestamp>
<X-Nonce>
<Idempotency-Key>
<bodySha256>
```
最终签名:
```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 服务发现。