二期内容提交
This commit is contained in:
@@ -0,0 +1,566 @@
|
||||
# 二期入库测量、计费与入库前端联调报告
|
||||
|
||||
> 版本日期:2026-08-04
|
||||
> 后端仓库:`D:\baiyue-firstleg`
|
||||
> 代码基线:`test-01` 分支,提交 `25d2a13`
|
||||
> 使用对象:平台后台前端、商户端前端、第三方测量系统联调人员
|
||||
|
||||
## 一、联调结论
|
||||
|
||||
二期后端主流程、平台测量、OCR 重量、客服审核、FBA 计费、费用发布、入库完成、库位和托盘维护、客户只读查询、第三方测量回传接口均已形成代码闭环。
|
||||
|
||||
最终正常业务流程是:
|
||||
|
||||
```text
|
||||
订单生成入库单
|
||||
→ 第三方或平台录入长宽高、箱规和件数
|
||||
→ 上传称重图片并执行 OCR
|
||||
→ 人工修正并确认 OCR 重量
|
||||
→ 保存测量草稿
|
||||
→ 提交测量
|
||||
→ 系统按托盘容量拆分托盘并立即占用库位
|
||||
→ 客服审核测量数据
|
||||
→ 审核通过后自动按 FBA 规则计费
|
||||
→ 自动发布费用给客户
|
||||
→ 自动完成入库
|
||||
→ 客户只读查看费用和入库结果
|
||||
```
|
||||
|
||||
联调时必须遵守以下口径:
|
||||
|
||||
- 客户不确认费用,前端不得设计或调用“客户确认费用”操作。
|
||||
- 测量提交后必须由客服审核;第三方回传测量数据不能直接审核、计费或完成入库。
|
||||
- 提交测量时已经完成托盘拆分和库位占用。客服驳回只退回测量修改,已占库位不释放。
|
||||
- 客服审核通过后,后端自动完成计费、内部确认、费用发布和入库,不需要前端依次点击计费、确认、发送。
|
||||
- 已发布费用可以调整;调整会生成新费用版本,但不会回退 `INBOUNDED` 入库状态。
|
||||
- 测量审核失败默认不允许异常放行。Nacos 配置 `firstleg.workflow.allow-rejected-measurement=false`,仅开启配置且有专门权限时才能放行。
|
||||
- 当前只实现 FBA 目的仓计费,不包含应收应付、对账、核销、发票、利润、PDA 和完整 WMS。
|
||||
|
||||
## 二、服务依赖、入口与文档
|
||||
|
||||
### 2.1 前端统一入口
|
||||
|
||||
本地默认网关:
|
||||
|
||||
```text
|
||||
http://127.0.0.1:9000
|
||||
```
|
||||
|
||||
聚合 Swagger:
|
||||
|
||||
```text
|
||||
http://127.0.0.1:9000/webjars/swagger-ui/index.html
|
||||
```
|
||||
|
||||
Swagger 分组:
|
||||
|
||||
- `firstleg-platform`:平台入库、测量、审核、计费及客户只读接口。
|
||||
- `firstleg-measurement-openapi`:第三方测量回传接口。
|
||||
- `warehouse-platform`:库位、托盘等仓储基础资料。
|
||||
- `file-platform`:测量图片上传、识别和读取。
|
||||
- `integration`:OCR 内部能力,前端一般不直接调用。
|
||||
|
||||
### 2.2 联调相关服务及默认端口
|
||||
|
||||
| 顺序 | 服务 | 默认端口 | 用途 |
|
||||
|---:|---|---:|---|
|
||||
| 1 | Nacos、MySQL、Redis | 环境配置 | 注册配置、数据和会话 |
|
||||
| 2 | `iam-service` | 9201 | 用户、角色、按钮权限 |
|
||||
| 3 | `auth-service` | 9100 | 登录、Token 刷新和退出 |
|
||||
| 4 | `warehouse-service` | 9205 | 仓库、库位、托盘及占用 |
|
||||
| 5 | `integration-service` | 9210 | OCR 识别能力 |
|
||||
| 6 | `file-service` | 9206 | 图片上传、持久化和 OCR 聚合 |
|
||||
| 7 | `firstleg-service` | 9202 | 入库、测量、审核和计费主流程 |
|
||||
| 8 | `gateway` | 9000 | 前端统一入口和聚合文档 |
|
||||
|
||||
若登录或订单前置流程依赖其他一期服务,应在 Firstleg 前启动。前端业务请求统一走网关,不要将各服务端口写死到页面。
|
||||
|
||||
## 三、全局请求与响应约定
|
||||
|
||||
### 3.1 平台和商户请求头
|
||||
|
||||
```http
|
||||
Authorization: Bearer <accessToken>
|
||||
Content-Type: application/json
|
||||
```
|
||||
|
||||
平台代目标企业操作的接口还必须传 `tenantId` 查询参数。后端同时校验登录应用类型、操作者数据范围、目标租户、仓库和资源归属;不能只依靠前端隐藏按钮实现隔离。
|
||||
|
||||
所有有副作用且要求幂等的接口必须传:
|
||||
|
||||
```http
|
||||
Idempotency-Key: <每次业务操作唯一且重试保持不变的字符串>
|
||||
```
|
||||
|
||||
网络超时重试时复用原键;用户重新发起一笔新操作时生成新键。
|
||||
|
||||
### 3.2 统一响应
|
||||
|
||||
```json
|
||||
{
|
||||
"code": "00000",
|
||||
"message": "成功",
|
||||
"data": {},
|
||||
"requestId": "...",
|
||||
"traceId": "...",
|
||||
"timestamp": "2026-08-04T02:38:31Z"
|
||||
}
|
||||
```
|
||||
|
||||
- 前端必须以业务 `code` 判断成功,不能只判断 HTTP 200。
|
||||
- 分页数据结构为 `data.records`、`data.page`、`data.size`、`data.total`、`data.pages`。
|
||||
- Long 类型 ID 已统一序列化为 JSON 字符串,前端必须按字符串保存和传递,不要转换成 JavaScript `Number`。
|
||||
- 401 统一跳转登录或触发刷新 Token;403 显示无权限;409 通常表示重复、状态冲突、乐观锁过期或资源已被占用,页面应刷新详情后再操作。
|
||||
- 提交按钮在请求完成前禁用,配合 `Idempotency-Key` 防止重复提交。
|
||||
|
||||
## 四、状态、页签和按钮规则
|
||||
|
||||
### 4.1 入库流程状态 `processStatus`
|
||||
|
||||
| 状态 | 中文含义 | 页面主要操作 |
|
||||
|---|---|---|
|
||||
| `WAIT_MEASURE` | 待测量 | 进入测量、录入草稿 |
|
||||
| `MEASURING` | 测量中 | 编辑草稿、OCR、人工修正、提交 |
|
||||
| `MEASURE_PENDING` | 待客服审核 | 客服审核通过或驳回 |
|
||||
| `MEASURE_REJECTED` | 测量驳回 | 修改并重新提交;特殊情况下异常放行 |
|
||||
| `WAIT_PRICING` | 待计费 | 正常流程由后台自动推进 |
|
||||
| `FEE_CONFIRMING` | 费用处理中 | 正常流程由后台自动推进 |
|
||||
| `INBOUNDING` | 入库处理中 | 正常流程由后台自动推进 |
|
||||
| `INBOUNDED` | 已入库 | 查看费用、库位、文件;有权限时调整费用 |
|
||||
| `VOIDED` | 已作废 | 只读 |
|
||||
|
||||
页面不要根据中文文案判断状态,必须使用状态码。
|
||||
|
||||
### 4.2 列表页签 `tab`
|
||||
|
||||
| 页签参数 | 后端包含状态 |
|
||||
|---|---|
|
||||
| `WAIT_MEASURE` | `WAIT_MEASURE`、`MEASURING`、`MEASURE_REJECTED` |
|
||||
| `WAIT_REVIEW` | `MEASURE_PENDING` |
|
||||
| `WAIT_FEE_SEND` | `WAIT_PRICING`、`FEE_CONFIRMING`、`INBOUNDING` |
|
||||
| `INBOUNDED` | `INBOUNDED` |
|
||||
|
||||
### 4.3 主流程状态变化
|
||||
|
||||
| 业务动作 | 结果状态 | 库位事实 |
|
||||
|---|---|---|
|
||||
| 保存测量草稿 | `MEASURING` | 未占用 |
|
||||
| 提交测量 | `MEASURE_PENDING` | 已拆托并确认 `OCCUPIED` |
|
||||
| 客服驳回 | `MEASURE_REJECTED` | 保持占用 |
|
||||
| 客服通过 | 自动经过计费和入库中间态 | 保持占用 |
|
||||
| 自动闭环完成 | `INBOUNDED`,费用 `SENT` | 保持占用 |
|
||||
| 调整已发布费用 | 仍为 `INBOUNDED`;新费用 `SENT`,旧费用 `SUPERSEDED` | 保持占用 |
|
||||
|
||||
## 五、平台入库单列表与详情
|
||||
|
||||
| 方法 | 路径 | 权限 |
|
||||
|---|---|---|
|
||||
| GET | `/api/platform/inbounds` | `platform:firstleg:inbound:list` |
|
||||
| GET | `/api/platform/inbounds/{inboundId}` | `platform:firstleg:inbound:view` |
|
||||
|
||||
共同参数:`tenantId`。列表支持:
|
||||
|
||||
```text
|
||||
inboundCode, orderNo, processStatus, orderStatus, measureReviewStatus,
|
||||
feeStatus, feeReviewStatus, tab, expectedArrivalFrom, expectedArrivalTo,
|
||||
customerId, customerName, companyId, warehouseId, deliveryWarehouseId,
|
||||
destinationCode, holdFlag, page, size
|
||||
```
|
||||
|
||||
列表已覆盖前端主要展示字段:入仓码、订单号、订单/流程/测量审核/费用状态、客户、公司、仓库、交货仓、FBA 目的仓、扣货、箱数、实重、净重、实测体积、材积重、计费重量、计费体积、托盘数、`locationCodes`、测量和入库完成时间、快递公司和快递单号、配载、集装箱、柜号、船名、航次、开船和到港时间、创建和更新时间。
|
||||
|
||||
详情在此基础上还返回:`publicRemark`、`palletLocations`、`files`、`internalNotes`、`statusHistory`。
|
||||
|
||||
## 六、平台测量与 OCR
|
||||
|
||||
接口前缀:
|
||||
|
||||
```text
|
||||
/api/platform/v1/first-leg/inbounds/{inboundId}
|
||||
```
|
||||
|
||||
| 方法 | 子路径 | 权限 | 说明 |
|
||||
|---|---|---|---|
|
||||
| GET | `/measurement` | `platform:firstleg:inbound:measure:view` | 获取测量、明细和 OCR 结果 |
|
||||
| PUT | `/measurement/draft` | `platform:firstleg:inbound:measure:edit` | 保存或更新草稿 |
|
||||
| POST | `/measurement/submit` | `platform:firstleg:inbound:measure:submit` | 提交并拆托、占库位 |
|
||||
| POST | `/weight-results` | `platform:firstleg:inbound:weight:edit` | 新增 OCR 结果 |
|
||||
| PUT | `/weight-results/{resultId}` | `platform:firstleg:inbound:weight:edit` | 人工修正重量 |
|
||||
| POST | `/weight-results/{resultId}/confirm` | `platform:firstleg:inbound:weight:confirm` | 确认最终重量 |
|
||||
|
||||
以上接口均带 `tenantId`;写接口按 Swagger 要求携带 `Idempotency-Key`。
|
||||
|
||||
### 6.1 保存测量草稿
|
||||
|
||||
```json
|
||||
{
|
||||
"measurementId": "已有草稿时传,首次可为空",
|
||||
"version": 0,
|
||||
"originalDocumentNo": "原单据号",
|
||||
"originalGrossWeightKg": 100.000,
|
||||
"originalVolumeCbm": 2.5000,
|
||||
"actualGoodsName": "实际品名",
|
||||
"items": [
|
||||
{
|
||||
"itemNo": "ITEM-001",
|
||||
"boxSpecification": "60*40*50cm",
|
||||
"packageCount": 10,
|
||||
"lengthCm": 60.000,
|
||||
"widthCm": 40.000,
|
||||
"heightCm": 50.000,
|
||||
"unitWeightKg": 12.500,
|
||||
"intelligentImageFileId": "图片文件ID",
|
||||
"overLimitWarning": "",
|
||||
"outerLengthCm": 61.000,
|
||||
"outerWidthCm": 41.000,
|
||||
"outerHeightCm": 51.000,
|
||||
"remark": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
注意:汇总体积、材积重等计算字段由后端计算,前端不要传入或自行覆盖。`items` 至少一条,尺寸和单件重量必须大于 0,`boxSpecification` 必填。
|
||||
|
||||
提交测量请求:
|
||||
|
||||
```json
|
||||
{
|
||||
"measurementId": "190000000000000001",
|
||||
"version": 1
|
||||
}
|
||||
```
|
||||
|
||||
`version` 是乐观锁版本。每次保存、修正或确认成功后都应使用响应中的新版本;409 后重新拉取详情,不能用旧版本强行重试。
|
||||
|
||||
### 6.2 OCR 重量
|
||||
|
||||
图片接口:
|
||||
|
||||
| 方法 | 路径 | 权限 |
|
||||
|---|---|---|
|
||||
| POST | `/api/platform/v1/files/measurements/{tenantId}/{inboundId}` | `platform:firstleg:inbound:weight:edit` |
|
||||
| POST | `/api/platform/v1/files/measurements/{tenantId}/{inboundId}/{fileId}/recognize` | `platform:firstleg:inbound:weight:edit` |
|
||||
| GET | `/api/platform/v1/files/measurements/{tenantId}/{inboundId}/{fileId}` | `platform:firstleg:inbound:measure:view` |
|
||||
|
||||
支持 JPG、PNG、BMP,单文件最大 5 MB。建议交互顺序:上传图片 → 调用识别 → 展示 OCR 原值、单位、置信度 → 用户按需修正 → 确认重量。
|
||||
|
||||
人工修正请求:
|
||||
|
||||
```json
|
||||
{
|
||||
"version": 0,
|
||||
"adjustedWeightValue": 12.500,
|
||||
"adjustedWeightUnit": "KG",
|
||||
"adjustmentReason": "OCR 小数点识别错误"
|
||||
}
|
||||
```
|
||||
|
||||
确认请求:
|
||||
|
||||
```json
|
||||
{
|
||||
"version": 1
|
||||
}
|
||||
```
|
||||
|
||||
测量详情会返回 OCR 原文、原重量和单位、置信度、人工修正值和原因、最终 KG、识别状态、修正/确认人员与时间、版本。多张秤图的最终重量由业务服务汇总,前端只负责展示和人工确认,不自行写汇总重量。
|
||||
|
||||
## 七、客服测量审核
|
||||
|
||||
| 方法 | 路径 | 权限 |
|
||||
|---|---|---|
|
||||
| POST | `/api/platform/v1/inbounds/{inboundId}/measurements/approve` | `firstleg:inbound:measurement-review` |
|
||||
| POST | `/api/platform/v1/inbounds/{inboundId}/measurements/reject` | `firstleg:inbound:measurement-review` |
|
||||
| POST | `/api/platform/v1/inbounds/{inboundId}/measurements/exception-release` | `firstleg:inbound:exception-release` |
|
||||
| GET | `/api/platform/v1/inbounds/{inboundId}/measurement-review-history` | `firstleg:inbound:measurement-review-history` |
|
||||
|
||||
写接口必须传 `tenantId` 和 `Idempotency-Key`。通过/驳回请求:
|
||||
|
||||
```json
|
||||
{
|
||||
"measurementId": "190000000000000001",
|
||||
"expectedVersion": 2,
|
||||
"reason": "审核意见,通过时可为空,驳回时按业务填写"
|
||||
}
|
||||
```
|
||||
|
||||
异常放行请求的 `reason` 必填:
|
||||
|
||||
```json
|
||||
{
|
||||
"measurementId": "190000000000000001",
|
||||
"expectedVersion": 3,
|
||||
"reason": "负责人审批后的异常放行原因"
|
||||
}
|
||||
```
|
||||
|
||||
审核通过请求成功即表示后端自动闭环已执行完成或可幂等续跑。前端应重新查询入库详情和最新费用,不要再自动串行调用 `/calculate`、`/confirm`、`/send`。
|
||||
|
||||
## 八、FBA 计费与费用调整
|
||||
|
||||
接口前缀:
|
||||
|
||||
```text
|
||||
/api/platform/v1/inbounds/{inboundId}/fees
|
||||
```
|
||||
|
||||
| 方法 | 子路径 | 权限 | 前端用途 |
|
||||
|---|---|---|---|
|
||||
| GET | `/latest` | `firstleg:fba-pricing:view` | 查询最新费用快照 |
|
||||
| PUT | `/published` | `firstleg:fba-pricing:adjust` | 调整已发布费用 |
|
||||
| POST | `/calculate` | `firstleg:fba-pricing:calculate` | 异常恢复/人工补偿,不是正常主流程按钮 |
|
||||
| POST | `/confirm` | `firstleg:fba-pricing:confirm` | 异常恢复/人工补偿 |
|
||||
| POST | `/send` | `firstleg:inbound:send-fee` | 异常恢复/人工补偿 |
|
||||
|
||||
调整已发布费用:
|
||||
|
||||
```json
|
||||
{
|
||||
"subtotalAmount": 1000.00,
|
||||
"additionalAmount": 50.00,
|
||||
"reason": "经审批调整附加费"
|
||||
}
|
||||
```
|
||||
|
||||
计费规则:
|
||||
|
||||
- 包税、按 KG:`计费数量 = max(实重, 实测 CBM × 换算系数)`。
|
||||
- 不包税、按 CBM:`计费数量 = max(实测 CBM, 实重 ÷ 换算系数)`。
|
||||
- 换算系数、精度、舍入方式、币种和价格均来自客户规则及船司费率版本,前端不能写死。
|
||||
|
||||
FBA 基础资料:
|
||||
|
||||
```text
|
||||
GET /api/platform/v1/fba-pricing/customer-rules
|
||||
GET /api/platform/v1/fba-pricing/customer-rules/{id}
|
||||
POST /api/platform/v1/fba-pricing/customer-rules/versions
|
||||
DELETE /api/platform/v1/fba-pricing/customer-rules/{id}
|
||||
|
||||
GET /api/platform/v1/fba-pricing/carrier-rates
|
||||
GET /api/platform/v1/fba-pricing/carrier-rates/{id}
|
||||
POST /api/platform/v1/fba-pricing/carrier-rates/versions
|
||||
DELETE /api/platform/v1/fba-pricing/carrier-rates/{id}
|
||||
```
|
||||
|
||||
规则采用版本化新增,历史订单费用使用当时快照,不应被新版本覆盖。
|
||||
|
||||
## 九、库位管理
|
||||
|
||||
| 方法 | 路径 | 权限 |
|
||||
|---|---|---|
|
||||
| GET | `/api/platform/v1/locations` | `platform:location:list` |
|
||||
| GET | `/api/platform/v1/locations/{id}` | `platform:location:list` |
|
||||
| GET | `/api/platform/v1/locations/available` | `platform:location:list` |
|
||||
| POST | `/api/platform/v1/locations` | `platform:location:create` |
|
||||
| PUT | `/api/platform/v1/locations/{id}` | `platform:location:edit` |
|
||||
| PATCH | `/api/platform/v1/locations/{id}/status` | `platform:location:status` |
|
||||
| DELETE | `/api/platform/v1/locations/{id}` | `platform:location:delete` |
|
||||
|
||||
分页条件:`tenantId`、`warehouseId`(必填)、`zoneCode`、`locationCode`、`locationType`、`status`、`page`、`size`。
|
||||
|
||||
库位类型:`SHELF`、`PALLET`、`FLOOR`、`FLOW_RACK`。查询状态:`AVAILABLE`、`RESERVED`、`OCCUPIED`、`DISABLED`。
|
||||
|
||||
新增/编辑请求:
|
||||
|
||||
```json
|
||||
{
|
||||
"warehouseId": "190000000000000002",
|
||||
"zoneCode": "A",
|
||||
"zoneName": "A库区",
|
||||
"locationCode": "A-001",
|
||||
"locationName": "A区001库位",
|
||||
"locationType": "PALLET",
|
||||
"maxWeightKg": 1000.000,
|
||||
"status": "AVAILABLE",
|
||||
"remark": "",
|
||||
"version": 0
|
||||
}
|
||||
```
|
||||
|
||||
启停仅允许 `AVAILABLE` 或 `DISABLED`,请求为:
|
||||
|
||||
```json
|
||||
{
|
||||
"status": "DISABLED",
|
||||
"version": 1
|
||||
}
|
||||
```
|
||||
|
||||
活动预占或已占用库位不能停用;删除要求库位已停用、无活动预占/占用,并在查询参数传 `warehouseId`、`version`。无普通空闲库位时,系统可使用本租户、本仓库的共享溢出位 `A0`,前端只展示后端结果。
|
||||
|
||||
## 十、托盘管理
|
||||
|
||||
| 方法 | 路径 | 权限 |
|
||||
|---|---|---|
|
||||
| GET | `/api/platform/v1/pallets` | `platform:pallet:list` |
|
||||
| GET | `/api/platform/v1/pallets/{id}` | `platform:pallet:list` |
|
||||
| POST | `/api/platform/v1/pallets` | `platform:pallet:create` |
|
||||
| PUT | `/api/platform/v1/pallets/{id}` | `platform:pallet:edit` |
|
||||
| DELETE | `/api/platform/v1/pallets/{id}` | `platform:pallet:delete` |
|
||||
|
||||
分页条件:`tenantId`、`warehouseId`、`palletCode`、`palletType`、`page`、`size`。新增/编辑请求:
|
||||
|
||||
```json
|
||||
{
|
||||
"warehouseId": "190000000000000002",
|
||||
"palletCode": "TP-001",
|
||||
"palletType": "木托盘",
|
||||
"capacityCbm": 2.000000,
|
||||
"remark": "标准托盘"
|
||||
}
|
||||
```
|
||||
|
||||
当前平台托盘 DTO 只返回主数据,不暴露内部 `AVAILABLE / RESERVED / OCCUPIED` 状态;也没有单独的托盘启停或状态筛选 API。页面本期只实现查询、新增、编辑、删除。已预占或占用托盘删除会被后端拒绝。这是当前明确的接口边界,不要自行猜测状态路径。
|
||||
|
||||
## 十一、商户端只读接口
|
||||
|
||||
```http
|
||||
GET /api/merchant/v1/first-leg/orders/{orderId}/inbound-billing
|
||||
```
|
||||
|
||||
权限:`merchant:order:view`。
|
||||
|
||||
返回 `inbound` 和 `latestFee`。仅允许商户身份查询本租户订单,且只有客服审核通过并已发布的费用可见。商户端只展示:入库结果、测量汇总、托盘/库位结果和最新费用,不提供费用确认、测量审核、费用修改操作。
|
||||
|
||||
## 十二、第三方测量开放接口
|
||||
|
||||
| 方法 | 路径 | 说明 |
|
||||
|---|---|---|
|
||||
| POST | `/openapi/v1/inbound-measurements` | 一次提交一条箱规明细和一张对应图片 |
|
||||
| GET | `/openapi/v1/inbound-measurements/events/{eventNo}` | 查询事件处理结果 |
|
||||
|
||||
请求头:
|
||||
|
||||
```http
|
||||
X-App-Key: <调用方应用键>
|
||||
X-Timestamp: <时间戳>
|
||||
X-Nonce: <随机串>
|
||||
X-Signature: <签名>
|
||||
Idempotency-Key: <幂等键>
|
||||
```
|
||||
|
||||
POST 使用 `multipart/form-data`,包含:
|
||||
|
||||
```text
|
||||
data:原始 UTF-8 JSON 文本
|
||||
image:该条箱规唯一对应的 JPG/PNG/BMP 图片,最大 5 MB
|
||||
```
|
||||
|
||||
`data` 示例:
|
||||
|
||||
```json
|
||||
{
|
||||
"eventNo": "M202608010001",
|
||||
"externalDetailNo": "BOX-20260801-0001",
|
||||
"warehouseCode": "CN-YW-01",
|
||||
"inboundCode": "RK202608010001",
|
||||
"inboundDetailId": "190000000000000001",
|
||||
"deviceCode": "DEVICE-01",
|
||||
"measuredAt": "2026-08-01T10:30:00+08:00",
|
||||
"boxSpecification": "60*40*50cm",
|
||||
"packageCount": 10,
|
||||
"lengthCm": 60.000,
|
||||
"widthCm": 40.000,
|
||||
"heightCm": 50.000,
|
||||
"unitWeightKg": 12.500,
|
||||
"remark": ""
|
||||
}
|
||||
```
|
||||
|
||||
绑定和签名规则:
|
||||
|
||||
- 后端先按 `inboundCode` 匹配入库单,再使用 `inboundDetailId` 通过 Order 内部接口校验该货物明细确实属于入库单关联订单;第三方不能自由创建来源不明的明细。
|
||||
- 一次请求只生成一个测量批次、一条测量明细和一个文件中心图片引用。
|
||||
- `externalDetailNo` 标识第三方物理测量明细;同一订单明细和相同第三方明细号不能重复绑定。
|
||||
- `eventNo` 在同一 `appKey` 下唯一;相同事件号或幂等键重试时,测量 JSON 和图片内容必须完全一致。
|
||||
- 签名 body 为:`data原文 + LF + image文件内容SHA-256小写十六进制`;再按既有六行 HMAC 规则计算 `X-Signature`。
|
||||
- `measuredAt` 必须带时区;长宽高大于 0;`packageCount` 为 1 至 1,000,000;`boxSpecification` 必填。
|
||||
- 图片由 File 服务验证真实格式并持久化;尺寸测量图片不触发重量 OCR。测量落库失败时会调用文件中心补偿清理。
|
||||
- 接口只接收测量数据,不推进客服审核、计费、库位或入库状态。
|
||||
|
||||
成功响应的 `data` 返回:`eventNo`、`inboundCode`、`inboundDetailId`、`measurementId`、`measurementItemId`、`imageFileId`、`accepted`、`status`、`receivedAt`。所有 ID 按字符串处理。
|
||||
|
||||
## 十三、推荐前端联调顺序
|
||||
|
||||
1. 验证平台登录、刷新 Token、退出、过期会话、401 和 403。
|
||||
2. 给测试角色同步二期权限,确认按钮显示与后端权限码一致。
|
||||
3. 初始化启用仓库、至少一个托盘、足量可用库位、客户计费规则和船司 FBA 费率。
|
||||
4. 创建或准备一条已进入待测量状态的真实入库单。
|
||||
5. 调用入库列表和详情,核对 Long ID 均按字符串处理。
|
||||
6. 上传秤图、调用 OCR、人工修正和确认重量。
|
||||
7. 保存测量草稿,刷新页面后核对草稿和版本回显。
|
||||
8. 提交测量,验证状态变为 `MEASURE_PENDING`,托盘和库位结果已生成。
|
||||
9. 先走一次驳回 → 修改 → 重新提交,验证旧页面版本提交被拒绝。
|
||||
10. 客服审核通过,随后重新查询详情和 `/fees/latest`,验证自动变为 `INBOUNDED / SENT`。
|
||||
11. 商户账号查询 `inbound-billing`,验证只读数据、跨租户 403/404 和未发布费用不可见。
|
||||
12. 调整一次已发布费用,验证新版本可见、旧版本被替代且入库状态不回退。
|
||||
13. 验证重复点击使用同一幂等键不会重复计费、重复托盘或重复占位。
|
||||
14. 验证无权限按钮、直接构造越权请求、空态、加载态、异常提示和移动端展示。
|
||||
|
||||
## 十四、测试数据准备清单
|
||||
|
||||
- 平台管理员账号:具备入库查看、测量、OCR、审核、计费查看/调整、库位和托盘权限。
|
||||
- 客服账号:具备测量审核权限,但不授予异常放行权限,用于验证按钮隔离。
|
||||
- 商户账号:绑定独立租户,只具备本企业订单查看权限。
|
||||
- 两个不同租户及各自仓库,用于跨企业越权验证。
|
||||
- 一个启用仓库、至少 2 个容量明确的托盘、至少 2 个 `AVAILABLE` 库位。
|
||||
- 一套生效中的客户 FBA 计费规则和匹配的船司费率,币种、渠道、目的仓、有效期完整。
|
||||
- 一条待测量入库单、一条已发布费用入库单。
|
||||
- JPG/PNG/BMP 各一张有效秤图,以及超 5 MB、伪造扩展名和无法识别图片各一张。
|
||||
- 第三方测量应用的 AppKey、签名密钥、绑定租户和仓库编码。
|
||||
|
||||
## 十五、本次回归测试结果
|
||||
|
||||
2026-08-04 11:42(Asia/Shanghai)在最终代码上重新执行:
|
||||
|
||||
```powershell
|
||||
D:\apache-maven-3.8.8\bin\mvn.cmd clean test -pl order,firstleg,warehouse,file,integration-service,gateway,iam -am
|
||||
```
|
||||
|
||||
结果:13 个 Reactor 模块全部 `SUCCESS`,`BUILD SUCCESS`,耗时约 1 分 5 秒。
|
||||
|
||||
| 模块 | 测试数 | Failures | Errors | Skipped |
|
||||
|---|---:|---:|---:|---:|
|
||||
| common-core | 3 | 0 | 0 | 0 |
|
||||
| common-web | 10 | 0 | 0 | 0 |
|
||||
| common-security | 6 | 0 | 0 | 0 |
|
||||
| common-openapi | 1 | 0 | 0 | 0 |
|
||||
| common-audit | 0 | 0 | 0 | 0 |
|
||||
| gateway | 10 | 0 | 0 | 0 |
|
||||
| iam | 79 | 0 | 0 | 0 |
|
||||
| order | 30 | 0 | 0 | 0 |
|
||||
| firstleg | 74 | 0 | 0 | 0 |
|
||||
| warehouse | 68 | 0 | 0 | 0 |
|
||||
| file | 23 | 0 | 0 | 0 |
|
||||
| integration-service | 8 | 0 | 0 | 0 |
|
||||
| **合计** | **312** | **0** | **0** | **0** |
|
||||
|
||||
重点自动化覆盖:
|
||||
|
||||
- 统一响应、异常映射、Long ID 字符串序列化和权限认证。
|
||||
- 测量草稿、OCR 修正/确认、提交及乐观锁。
|
||||
- 测量提交时托盘拆分、库位预占/确认、失败回滚、幂等重试。
|
||||
- 客服驳回、通过后自动计费/发布/入库、中断续跑。
|
||||
- FBA 计费规则选择、已发布费用调整和状态不回退。
|
||||
- 客户只见本租户已发布费用,平台/商户身份及跨租户隔离。
|
||||
- 第三方签名、防重放、事件幂等、仓库范围及不越权推进状态。
|
||||
- 第三方一条箱规对应一条订单货物明细和一张图片、重复绑定拦截、图片补偿清理。
|
||||
- 库位/托盘主数据约束、A0 共享溢出及活动占用唯一性契约。
|
||||
- 图片上传类型、大小、真实图片校验、下载三字段隔离和 OCR 聚合。
|
||||
- Gateway 路由以及 OpenAPI、Application、Nacos YAML 解析。
|
||||
|
||||
测试日志中的模拟数据库不可用、文件写入失败等异常堆栈,是异常映射测试主动构造的场景;对应测试均通过,不是构建失败。
|
||||
|
||||
## 十六、尚需在线环境验证与当前遗留
|
||||
|
||||
本次回归没有启动服务、没有占用 `9000/9100/920x` 默认端口,也没有连接或修改 MySQL。以下内容必须在测试环境由后端与前端共同验证后,才能判定上线验收通过:
|
||||
|
||||
1. 二期 SQL 在真实 MySQL 的建表/迁移执行,以及历史数据与唯一约束冲突检查。
|
||||
2. Gateway、Nacos、Redis、MySQL、服务间令牌和各微服务的真实 HTTP 联通。
|
||||
3. OCR 引擎对现场秤图的识别准确率、超时和失败重试。
|
||||
4. 并发提交、并发抢托盘/库位、A0 多托盘共享在真实数据库事务下的压力验证。
|
||||
5. 浏览器端完整登录、刷新 Token、权限按钮、403、跨租户、空态、加载态和重复提交体验。
|
||||
6. 当前托盘管理没有独立启停和状态筛选接口;本期页面只做查询、新增、编辑、删除。
|
||||
|
||||
因此当前结论是:**代码编译及 312 项单元/契约测试全部通过,可以进入前后端测试环境联调;真实数据库迁移和在线 HTTP 业务验收尚未执行。**
|
||||
@@ -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 服务发现。
|
||||
Reference in New Issue
Block a user