docs(changelog): #9201 核单车辆费用写入明细条数上限(内部接口,order-v3)
changelog-filename-gate / validate (push) Failing after 1s
changelog-filename-gate / validate (push) Failing after 1s
这个提交包含在:
@@ -0,0 +1,202 @@
|
|||||||
|
---
|
||||||
|
schema: "hl-changelog/v2"
|
||||||
|
ticket: "9201"
|
||||||
|
title: "核单车辆费用写入内部接口:明细条数上限 2000,超限写库前失败 605610"
|
||||||
|
consumer: "internal"
|
||||||
|
author: "chewu3(GIT)"
|
||||||
|
change_type: "修改接口"
|
||||||
|
backend_status: "deployed"
|
||||||
|
gateway_status: "not_required"
|
||||||
|
frontend_status: "not_required"
|
||||||
|
frontend_owner: ""
|
||||||
|
frontend_ref: ""
|
||||||
|
target_release: ""
|
||||||
|
verified_at: ""
|
||||||
|
status_note: "唯一调用方 order-v3 的合法载荷上界 2000 条(逐日派车拓扑不变量);当前冻结写入口另有更紧的 1000 条审计上限。超限在首次数据库副作用之前抛 605610,消息含实际条数与上限,零部分写入。已随 dev-v3 57de68b70 部署 TEST(task 5c950897),两实例 UP。"
|
||||||
|
updated_at: "2026-10-23"
|
||||||
|
base: "dev-v3"
|
||||||
|
---
|
||||||
|
|
||||||
|
# hl-fleet-service: 核单车辆费用写入明细条数设上限
|
||||||
|
|
||||||
|
> **服务**: hl-fleet-service(内部 Feign 口,网关不放行)
|
||||||
|
> **PR**: `#9208`(已合入 `dev-v3`,合并提交 `57de68b70`)
|
||||||
|
> **Issue**: #9201
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ⚠️ 关键变化
|
||||||
|
|
||||||
|
🟢 `POST /internal/fleet/reconciliation/vehicle-fee` 的 `items` 新增条数上限 **2000**:超过即返回 `605610`,且在取数据库栅栏、判关账、任何 upsert 之前失败——**不会出现部分明细已写入**。
|
||||||
|
|
||||||
|
🟢 锁租约由 30s 延长到 120s(服务端 Redis 租约,调用方不可见)。原因:2000 条最坏 7 次 DB 往返/条 ≈ 14000 次,30s 在慢库下不够。
|
||||||
|
|
||||||
|
🔴 此前无上限:调用方传入多少条就逐条写多少条,持锁时间随笔数线性增长,可能超过租约后仍继续写库。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 二、变更接口清单
|
||||||
|
|
||||||
|
| # | 接口 | 方法 | 路径 | 变更类型 | 说明 |
|
||||||
|
|---|------|------|------|----------|------|
|
||||||
|
| 1 | 核单车辆费用写入 | POST | `/internal/fleet/reconciliation/vehicle-fee` | 请求体新增条数上限 | items > 2000 返回 605610,零写入 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 三、接口详情
|
||||||
|
|
||||||
|
### 1. 核单车辆费用写入 `POST /internal/fleet/reconciliation/vehicle-fee`
|
||||||
|
|
||||||
|
**VO**: `SettlementVehicleFeeWriteDTO`(hl-common-core,唯一调用方 order-v3)
|
||||||
|
|
||||||
|
#### 使用场景
|
||||||
|
|
||||||
|
order-v3 结账冻结车辆费用后,经命令发件箱异步把逐日车辆费用明细回传车务落库(`OrderFleetCommandOutboxProcessor` → `FleetAssignmentExpandFeignClient#writeSettlementVehicleFee`)。一条 `items` 对应订单侧一条 `FLEET_DAILY` 冻结行,服务端逐条按 `(orderId, assignmentId, serviceDate)` 唯一键 upsert。
|
||||||
|
|
||||||
|
#### 入参
|
||||||
|
|
||||||
|
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|
||||||
|
|------|------|------|------|------|------|
|
||||||
|
| orderId / orderNo | Body | Long / String | ✅ | - | 订单身份 |
|
||||||
|
| settledAt | Body | LocalDateTime | 否 | - | 结账时刻 |
|
||||||
|
| items | Body | List | ✅ | **1~2000 条**(新增上限) | 每条一个 assignmentId + serviceDate |
|
||||||
|
| items[].assignmentId | Body | Long | ✅ | - | 车务派车行 ID |
|
||||||
|
| items[].driverId | Body | Long | ✅ | - | 司机 |
|
||||||
|
| items[].serviceDate | Body | LocalDate | ✅ | - | 服务日 |
|
||||||
|
| items[].actualAmount | Body | BigDecimal | ✅ | 两位小数 | 该日实际车费 |
|
||||||
|
| items[].vehicleId | Body | Long | 否 | - | 为空时跳过车辆快照校验 |
|
||||||
|
|
||||||
|
#### 出参
|
||||||
|
|
||||||
|
| 字段 | 类型 | 说明 |
|
||||||
|
|------|------|------|
|
||||||
|
| code / message / success | - | 成功 `200`;超限 `605610` |
|
||||||
|
| data | null | 本接口无业务返回体 |
|
||||||
|
|
||||||
|
#### 请求示例
|
||||||
|
|
||||||
|
```http
|
||||||
|
POST /internal/fleet/reconciliation/vehicle-fee HTTP/1.1
|
||||||
|
Host: hl-fleet-service
|
||||||
|
Content-Type: application/json
|
||||||
|
|
||||||
|
{
|
||||||
|
"orderId": 2112275846201069569,
|
||||||
|
"orderNo": "SO202610230001",
|
||||||
|
"settledAt": "2026-10-23 10:30:00",
|
||||||
|
"items": [
|
||||||
|
{ "assignmentId": 9001, "driverId": 501, "serviceDate": "2026-10-28", "actualAmount": "1500.00", "vehicleId": 4401 }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 响应示例
|
||||||
|
|
||||||
|
```json
|
||||||
|
{ "code": 200, "message": "成功", "data": null, "success": true }
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 空数据 / 降级响应
|
||||||
|
|
||||||
|
`items` 为空数组时维持原行为:只记一条 info 日志,直接返回 `200`,不写库。服务端降级不适用于本接口(失败即整笔失败,由发件箱重放)。
|
||||||
|
|
||||||
|
#### 错误响应
|
||||||
|
|
||||||
|
超过 2000 条(新增):
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 605610,
|
||||||
|
"message": "核单车辆费用写入明细条数超过上限:实际 2001 条,上限 2000 条(orderId=… orderNo=…)",
|
||||||
|
"success": false,
|
||||||
|
"data": null
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
单条结构非法(645610 同码族、原有行为):`605610` + 具体缺字段说明。
|
||||||
|
|
||||||
|
#### 业务边界
|
||||||
|
|
||||||
|
- 条数检查在 `@Lock4j` 租约之后(AOP 时序)、数据库栅栏与全部写库之前;超限请求不产生任何行级写入。
|
||||||
|
- 上限 2000 取自 `FleetAssignmentPlanConstants.MAX_DAILY_PLAN_CELLS`(逐日派车拓扑硬不变量),不是拍脑袋值:调用方每条 items 来自一条冻结 `FLEET_DAILY` 行,而冻结行数受同一拓扑校验约束。
|
||||||
|
- 当前冻结写入口另按 `VehicleFeeAuditSnapshot.MAX_AUDIT_ITEMS = 1000` 限制,故正常新单实际不会到 2000;上限留 2 倍余量,避免误拒更早历史数据形成的大单。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 四、契约约束与正确调用方式
|
||||||
|
|
||||||
|
- 一次请求的 `items` 必须 ≤ 2000,且**不要**把「整团全部历史日期」拼进同一次写入来绕开分页。
|
||||||
|
- 超限返回是**可判定的拒绝**:调用方应记录实际条数并告警,不要盲目重试同一 payload(重试仍 605610)。
|
||||||
|
- 调用方 Feign 默认 `readTimeout=30000`:2000 条慢库档(3~5ms/次往返)可能超过 30s,超时后按发件箱重放;服务端 upsert 以 `(orderId, assignmentId, serviceDate)` 唯一键收敛,重放不会产生重复行。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 五、数据库行为
|
||||||
|
|
||||||
|
| 场景 | 结果 |
|
||||||
|
|------|------|
|
||||||
|
| items ≤ 2000 | 逐条 upsert(先按唯一键查,有则 update,无则 insert) |
|
||||||
|
| items > 2000 | 零写入,直接 605610 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 六、边界行为
|
||||||
|
|
||||||
|
- 恰好 2000 条:照常写入(上限含边界)。
|
||||||
|
- `items=null` 或空数组:不触发上限检查,原行为返回 200。
|
||||||
|
- 判断发生在 Redis 租约之后是已知取舍(租约由 AOP 在方法体前取得);业务副作用(数据库栅栏、关账判断、写行)全在检查之后。
|
||||||
|
- 锁键 `fleet:recon-vehicle-fee:{orderId}`、事务边界、章节锁序均未改,仅 `expire` 30s→120s。
|
||||||
|
|
||||||
|
## 六.6、修改前后对比
|
||||||
|
|
||||||
|
| 场景 | 改前 | 改后 |
|
||||||
|
|------|------|------|
|
||||||
|
| items = 2001 | 逐条写 2001 行,持锁时间不受控 | 605610,零写入 |
|
||||||
|
| items = 2000 | 逐条写入(可能超过 30s 租约) | 逐条写入,租约 120s |
|
||||||
|
| items 为空 | 200,info 日志 | 同左(不变) |
|
||||||
|
|
||||||
|
## 六.7、影响评估
|
||||||
|
|
||||||
|
- **是否破坏向后兼容**:否。唯一调用方 order-v3 的合法载荷上界 2000,正常链路不触发新拒绝。
|
||||||
|
- **调用方是否必须同步上线**:否。
|
||||||
|
- **回滚**:revert #9208 并重部署 hl-fleet-service,恢复 30s 租约与无上限行为。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 七、不影响范围
|
||||||
|
|
||||||
|
- 其他 `/internal/fleet/reconciliation/**` 接口、保险费用写入、关账判断逻辑。
|
||||||
|
- 单条 upsert 语义与唯一键(未改动)。
|
||||||
|
- 管理后台核单页面与前端契约(零变化)。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 八、测试环境已验证
|
||||||
|
|
||||||
|
**环境**:TEST **验证时间**:2026-10-23 10:39~10:45
|
||||||
|
**构建身份**:hl-fleet-service `dev-v3 @ 57de68b70`,部署 task `5c950897`;两实例 `8187/8087 is UP after 7s`;部署状态回读 `dev-v3 57de68b70 0/N ok`。
|
||||||
|
|
||||||
|
本接口不经网关、唯一调用方为 order-v3,测试服未做载荷级直连实测;契约由以下读数支撑:
|
||||||
|
|
||||||
|
- 单元:`ReconciliationVehicleFeeServiceTest` 14 例 0 失败,含 `write_itemsOverCap_throws605610BeforeAnyFenceOrWrite`(2001 条 → 605610、消息含 2001/2000、断言零 DB 交互)与 `write_itemsAtCap_passesGuardAndWritesEveryRow`(2000 条照常写 2000 行)。
|
||||||
|
- 变异:把边界改成 `<= 上限+1` → 两条用例红(`Expecting code to raise a throwable`),还原后 14 例复绿。
|
||||||
|
- 冒烟:网关只读探活 `GET /admin/fleet/dispatch-groups/page` 200 + 3 个团详情 200(`CHECKED=3 FAIL=0`);两实例部署窗口日志 `total_ERROR=0`。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 十、相关文档
|
||||||
|
|
||||||
|
- Issue `#9201`;PR `#9208`
|
||||||
|
- 门禁与量化过程见 `E:/work/reports/9201/收口报告.md`(2000 上界推导、单条 3/5/7 次 DB 往返、租约余量)
|
||||||
|
|
||||||
|
## 关联 / 联系人
|
||||||
|
|
||||||
|
### 链接
|
||||||
|
|
||||||
|
- **Issue**: [#9201](https://git.1814.love/wx/HL/issues/9201)
|
||||||
|
- **PR**: [#9208](https://git.1814.love/wx/HL/pulls/9208)
|
||||||
|
- **Merge commit**: [57de68b70](https://git.1814.love/wx/HL/commit/57de68b7033de26be47c3d96fb324fd375352c0a)
|
||||||
|
|
||||||
|
### 联系人
|
||||||
|
|
||||||
|
- **后端负责人**: @chewu3
|
||||||
在新工单中引用
屏蔽一个用户