- #2521 list 路径迁移 /transport-plans → /transport-plan/list - #2522 add 路径 + 错误码 581140-581143 + @Idempotent + @Lock4j - #2523 edit Method PUT → POST + 错误码 581144 - #2524 delete Method DELETE → POST + 桥接表级联软删 测试服 9443 真测全 ✅ (commit 15e2c7eeb 含 hotfix #2550) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
8.3 KiB
大交通批次编辑: Method 破坏性变更 (PUT → POST) + 错误码 581144
存放目录: 二期(v3,
order-v3标签)→changelogs-v2/2026-05/服务: hl-order-v3 (端口 8084) PR: #2546 (commit
1106b9d7f) + hotfix #2550 (commit15e2c7eeb修 conflict markers) Issue: #2523 日期: 2026-05-18 影响范围: 管理后台「行程安排 - 接送站」区块 - 大交通批次编辑弹窗
⚠️ 关键变化(破坏性 - 前端必同步)
一行红字说清:
- 本次变了什么: 编辑大交通批次接口 Method 从
PUT改为POST+ 路径子段从/transport-plans/{planId}改为/transport-plan/{planId}/edit - 前端以前以为的是什么:
PUT /v3/admin/order/{id}/transport-plans/{planId} - 实际现在是什么:
POST /v3/admin/order/{id}/transport-plan/{planId}/edit
Method 变化是头号陷阱: 前端若沿用 axios.put(...) 会直接 404 / 405,必须改 axios.post(...)。
配套破坏性: §2.6 大交通批次模块 4 个接口路径风格 + 动词 + Method 全部统一迁移,见 #2521/#2522/#2524。
一、背景
V5.48 §2.6 统一管理后台所有变更动作走 POST /{资源}/{动词} 风格(对齐订单核心模块、出行人模块):
| 旧风格 (REST 经典) | 新风格 (V5.48 统一) |
|---|---|
PUT /transport-plans/{id} |
POST /transport-plan/{id}/edit |
DELETE /transport-plans/{id} |
POST /transport-plan/{id}/delete |
POST + 动词路径的好处: 网关 RBAC 配置统一只看 path 不看 Method、操作审计日志埋点统一、防误触缓存。
#2550 hotfix 修复合并 dev-v3 时 IDE 残留的 <<<<<<< conflict markers(扫描全代码确保零残留)。
二、变更接口清单
| # | 接口 | 方法 | 路径 | 变更类型 | 说明 |
|---|---|---|---|---|---|
| 1 | 大交通批次编辑 | POST | /v3/admin/order/{id}/transport-plan/{planId}/edit |
Method + 路径双破坏 | 旧 PUT /transport-plans/{planId} 下线;新增错误码 581144 |
三、接口详情
1. 大交通批次编辑 POST /v3/admin/order/{id}/transport-plan/{planId}/edit
VO: TransportPlanReqVO(入参,与 add 同 VO) / TransportPlanVO(出参)
入参
| 字段 | 位置 | 类型 | 必填 | 说明 |
|---|---|---|---|---|
id |
Path | Long | ✅ | 订单 ID |
planId |
Path | Long | ✅ | 批次 ID |
| Body | Body | TransportPlanReqVO |
✅ | 字段同 add(见 #2522 入参表) |
出参 Result<TransportPlanVO>
字段同 #2521 列表元素结构。
请求示例
POST /v3/admin/order/60123456789012/transport-plan/80012345/edit
{
"direction": "ARRIVAL",
"transportType": "FLIGHT",
"transportNo": "CA1234",
"carrier": "中国国际航空",
"departStation": "北京首都T3",
"arriveStation": "长春龙嘉",
"departTime": "2026-06-01T09:00:00",
"arriveTime": "2026-06-01T11:00:00",
"selfDrivePeriod": null,
"selfDriveEta": null,
"travelerIds": [70123456789012, 70123456789013, 70123456789014],
"remark": "改时间,加一人"
}
响应示例
{
"code": 200,
"data": {
"id": "80012345",
"orderId": "60123456789012",
"direction": "ARRIVAL",
"transportType": "FLIGHT",
"transportNo": "CA1234",
"carrier": "中国国际航空",
"departStation": "北京首都T3",
"arriveStation": "长春龙嘉",
"departTime": "2026-06-01T09:00:00",
"arriveTime": "2026-06-01T11:00:00",
"selfDrivePeriod": null,
"selfDriveEta": null,
"travelers": [
{"id": "70123456789012", "name": "张三"},
{"id": "70123456789013", "name": "王小明"},
{"id": "70123456789014", "name": "李四"}
],
"remark": "改时间,加一人"
},
"msg": "success"
}
错误响应
复用 add 接口的 4 个错误码(581140-581143),并新增:
| 错误码 | 含义 | 触发场景 |
|---|---|---|
581144 |
plan 不存在 / 不属于该订单 | planId 无效 / 已软删 / order_id 与 path 不匹配 |
示例 581144:
{ "code": 581144, "msg": "大交通批次不存在或不属于该订单: planId=80099999, orderId=60123456789012", "data": null }
四、契约约束与正确调用方式
✅ 正确 / ❌ 错误调用对照
| 场景 | 请求 | 结果 |
|---|---|---|
| ✅ 新调用 | POST /transport-plan/80012345/edit + Body |
200 |
| ❌ 沿用旧 PUT | PUT /transport-plans/80012345 + Body |
404 / 405 |
| ❌ planId 跨单 | edit path orderId=A,planId 属于 orderId=B |
581144 |
| ❌ planId 已软删 | edit 已 deleted=1 的 plan | 581144 |
全量重建语义
- edit 不是"增量改字段": Body 提交什么就最终持久化什么(包括
travelerIds全量) - 想去掉 1 个 traveler: 在
travelerIds数组中删掉该 ID 再提交,后端会 DELETE+INSERT 重建桥接表 - 不能用 edit "局部改 1 个字段而保留其他字段不动"的语义,前端必须先 GET list 取完整 plan 再改
并发与幂等
@Lock4j(keys = "#id"): 同订单 30 秒锁,串行化 add/edit/delete- 编辑接口不加
@Idempotent(每次编辑都应被尊重,允许相同 Body 重复提交以触发 update_time 刷新)
五、数据库行为
单事务执行,失败整体回滚:
| 步骤 | 表 | 动作 |
|---|---|---|
| 1 | order_transport_plan |
UPDATE 该 plan 行所有业务字段(direction / transportType / transportNo / carrier / 各 station / 各 time / selfDrive* / remark),update_time 自动刷新 |
| 2 | order_transport_plan_traveler |
DELETE(物理删除桥接行 WHERE plan_id = ?) |
| 3 | order_transport_plan_traveler |
INSERT N 行(N = 新 travelerIds.size()) |
桥接表用「全量重建」而非 diff 算法,简化业务逻辑;桥接表本身无业务历史价值,物理删除不留痕。
order_transport_plan主表的update_time会随 edit 刷新供前端展示「最后修改时间」。
校验顺序(同 add,附加 plan 归属校验):
- plan 存在性 + 归属 →
581144 - 订单存在性 →
581100 - 字段组合 →
581140 - 时间顺序 →
581142 - travelerIds 归属 →
581141 - 同方向同 traveler 占用(排除自己)→
581143
六、边界行为
- 未登录 → 401(网关拦截)
- planId 不存在 / 跨单 / 已软删 →
581144 - 同方向同 traveler 占用判定排除当前 plan 自己(改自己不算冲突,只跟其他 active plan 比对)
- Body 与原 plan 完全相同(无字段变化)→ 200 + 桥接表 DELETE+INSERT(等价 no-op 但
update_time仍刷新) - 老数据(v2 历史 plan 字段缺失)→ edit 会以 Body 全量值覆盖
七、不影响范围
- 仅影响: 管理后台 F21 「接送站」编辑弹窗
- 零影响:
- C 端订单详情 / mp 抵达计划接口
- 出行人 CRUD(traveler 表本身不动)
- 订单状态机
- 操作日志(本接口暂不挂
@OperationLog,与 v2 行为一致)
八、测试环境已验证
测试服 9443 网关 + 真 admin token round-trip 验证:
POST https://web.test.1814.love:9443/v3/admin/order/{orderId}/transport-plan/{planId}/edit
→ 200 + 返回更新后 plan(travelers 重建)✓
→ planId 不存在 → 581144 ✓
→ planId 属于别单 → 581144 ✓
→ 业务校验复用 581140/581141/581142/581143 全覆盖 ✓
→ 同方向同 traveler 占用判定排除自己 → 200 ✓
→ 桥接表全量 DELETE+INSERT 重建生效 ✓
hotfix #2550(commit 15e2c7eeb)清理了合并 dev-v3 时残留的 <<<<<<< / ======= / >>>>>>> conflict markers(本接口实现文件曾受影响,hotfix 后 mvn compile 通过 + 9443 验证通过)。
九、相关历史 PR
| PR | Issue | 说明 | 是否仍有效 |
|---|---|---|---|
| 本 PR #2546 | #2523 | Method PUT→POST + 路径迁移 + 错误码 581144 | ✅ 最新 |
| #2550 | (hotfix) | 修复 conflict markers,无功能变化 | ✅ 配套 |
十、相关文档
- 关联 Issue: wx/HL#2523
- 关联 PR: wx/HL#2546
- Hotfix PR: wx/HL#2550
- API 文档:
docs/order-v3/api/API-SPEC-V5.48.html§2.6.3 - 配套破坏性: 见
18_#2521_transport-plan-list-path.md/18_#2522_transport-plan-add.md/18_#2524_transport-plan-delete.md