8.4 KiB
schema, ticket, title, consumer, author, change_type, backend_status, gateway_status, frontend_status, frontend_owner, frontend_ref, target_release, verified_at, status_note, updated_at, base
| schema | ticket | title | consumer | author | change_type | backend_status | gateway_status | frontend_status | frontend_owner | frontend_ref | target_release | verified_at | status_note | updated_at | base |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| hl-changelog/v2 | 9201 | 核单车辆费用写入内部接口:明细条数上限 2000,超限写库前失败 605610 | internal | chewu3(GIT) | 修改接口 | deployed | not_required | not_required | 唯一调用方 order-v3 的合法载荷上界 2000 条(逐日派车拓扑不变量);当前冻结写入口另有更紧的 1000 条审计上限。超限在首次数据库副作用之前抛 605610,消息含实际条数与上限,零部分写入。已随 dev-v3 57de68b70 部署 TEST(task 5c950897),两实例 UP。 | 2026-10-23 | 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 | 本接口无业务返回体 |
请求示例
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 }
]
}
响应示例
{ "code": 200, "message": "成功", "data": null, "success": true }
空数据 / 降级响应
items 为空数组时维持原行为:只记一条 info 日志,直接返回 200,不写库。服务端降级不适用于本接口(失败即整笔失败,由发件箱重放)。
错误响应
超过 2000 条(新增):
{
"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}、事务边界、章节锁序均未改,仅expire30s→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,测试服未做载荷级直连实测;契约由以下读数支撑:
- 单元:
ReconciliationVehicleFeeServiceTest14 例 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/page200 + 3 个团详情 200(CHECKED=3 FAIL=0);两实例部署窗口日志total_ERROR=0。
十、相关文档
- Issue
#9201;PR#9208 - 门禁与量化过程见
E:/work/reports/9201/收口报告.md(2000 上界推导、单条 3/5/7 次 DB 往返、租约余量)
关联 / 联系人
链接
联系人
- 后端负责人: @chewu3