From 1001a07f4493ff0077bcba85e85ab7b9570e972e Mon Sep 17 00:00:00 2001 From: API Changelog Bot Date: Mon, 18 May 2026 20:24:44 +0800 Subject: [PATCH] =?UTF-8?q?changelog(order-v3):=20=E5=A4=A7=E4=BA=A4?= =?UTF-8?q?=E9=80=9A=E6=89=B9=E6=AC=A1=204=20=E6=8E=A5=E5=8F=A3=20(#2521-#?= =?UTF-8?q?2524)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - #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) --- .../18_#2521_transport-plan-list-path.md | 238 +++++++++++++++++ .../2026-05/18_#2522_transport-plan-add.md | 239 ++++++++++++++++++ .../2026-05/18_#2523_transport-plan-edit.md | 234 +++++++++++++++++ .../2026-05/18_#2524_transport-plan-delete.md | 187 ++++++++++++++ 4 files changed, 898 insertions(+) create mode 100644 changelogs-v2/2026-05/18_#2521_transport-plan-list-path.md create mode 100644 changelogs-v2/2026-05/18_#2522_transport-plan-add.md create mode 100644 changelogs-v2/2026-05/18_#2523_transport-plan-edit.md create mode 100644 changelogs-v2/2026-05/18_#2524_transport-plan-delete.md diff --git a/changelogs-v2/2026-05/18_#2521_transport-plan-list-path.md b/changelogs-v2/2026-05/18_#2521_transport-plan-list-path.md new file mode 100644 index 0000000..515ce9f --- /dev/null +++ b/changelogs-v2/2026-05/18_#2521_transport-plan-list-path.md @@ -0,0 +1,238 @@ +# 大交通批次列表: 接口路径破坏性迁移 (transport-plans → transport-plan/list) + +> **存放目录**: 二期(v3,`order-v3` 标签)→ `changelogs-v2/2026-05/` +> +> **服务**: hl-order-v3 (端口 8084) +> **PR**: #2544 (squash 后 commit `be3badaca`) +> **Issue**: #2521 +> **日期**: 2026-05-18 +> **影响范围**: 管理后台「行程安排 - 接送站」区块 - 大交通批次列表 + +--- + +## ⚠️ 关键变化(破坏性 - 前端必同步) + +一行红字说清: +- **本次变了什么**: 大交通批次列表接口路径从 `/transport-plans` 改为 `/transport-plan/list` +- **前端以前以为的是什么**: `GET /v3/admin/order/{id}/transport-plans` (旧式复数资源风格) +- **实际现在是什么**: `GET /v3/admin/order/{id}/transport-plan/list` (统一 list/add/edit/delete 动词风格) + +**配套破坏性**: §2.6 大交通批次模块 4 个接口路径风格 + 动词 + Method 全部统一迁移,见 #2522/#2523/#2524 changelog。前端需要 4 个接口一并改。 + +--- + +## 一、背景 + +V5.48 §2.6 大交通批次模块 4 个接口(list/add/edit/delete)统一为 `/transport-plan/{动词}` 子路径风格,与订单模块其他子资源(出行人 traveler/list、配房 hotel-requirement 等)保持一致: + +| 风格 | 旧 (V5.47 之前) | 新 (V5.48) | +|------|-----------------|------------| +| 列表 | `GET /transport-plans` | `GET /transport-plan/list` | +| 新增 | `POST /transport-plans` | `POST /transport-plan/add` | +| 编辑 | `PUT /transport-plans/{planId}` | `POST /transport-plan/{planId}/edit` | +| 软删 | `DELETE /transport-plans/{planId}` | `POST /transport-plan/{planId}/delete` | + +统一动词路径后,网关路由 / 权限 RBAC / 操作审计的资源前缀一致,便于配置和扫描。 + +--- + +## 二、变更接口清单 + +| # | 接口 | 方法 | 路径 | 变更类型 | 说明 | +|---|------|------|------|----------|------| +| 1 | 大交通批次列表 | GET | `/v3/admin/order/{id}/transport-plan/list` | 路径迁移 | 旧路径 `/transport-plans` 已下线 | + +--- + +## 三、接口详情 + +### 1. 大交通批次列表 `GET /v3/admin/order/{id}/transport-plan/list` + +**VO**: `TransportPlanVO` + +#### 入参 + +| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 | +|------|------|------|------|------|------| +| `id` | Path | Long | ✅ | - | 订单 ID | + +#### 出参 `Result>` + +| 字段 | 类型 | 说明 | +|------|------|------| +| `id` | String | 批次 ID(雪花) | +| `orderId` | String | 订单 ID | +| `direction` | String | 方向 `ARRIVAL` / `DEPARTURE` | +| `transportType` | String | 类型 `FLIGHT` / `TRAIN` / `SELF_DRIVE` | +| `transportNo` | String | 航班号 / 车次号(SELF_DRIVE 时为 null) | +| `carrier` | String | 航司 / 铁路公司 | +| `departStation` | String | 出发站 | +| `arriveStation` | String | 到达站 | +| `departTime` | LocalDateTime | 出发时间(SELF_DRIVE 时为 null) | +| `arriveTime` | LocalDateTime | 到达时间(SELF_DRIVE 时为 null) | +| `selfDrivePeriod` | String | 仅 SELF_DRIVE: `MORNING` / `AFTERNOON` / `EVENING` | +| `selfDriveEta` | LocalDateTime | 仅 SELF_DRIVE: 预计抵达时间 | +| `travelers` | List | 桥接表关联出行人,元素 `{id, name}` | +| `remark` | String | 备注 | + +#### 请求示例 + +``` +GET /v3/admin/order/60123456789012/transport-plan/list +``` + +#### 响应示例 + +```json +{ + "code": 200, + "data": [ + { + "id": "80012345", + "orderId": "60123456789012", + "direction": "ARRIVAL", + "transportType": "FLIGHT", + "transportNo": "CA1234", + "carrier": "中国国际航空", + "departStation": "北京首都T3", + "arriveStation": "长春龙嘉", + "departTime": "2026-06-01T08:30:00", + "arriveTime": "2026-06-01T10:15:00", + "selfDrivePeriod": null, + "selfDriveEta": null, + "travelers": [ + {"id": "70123456789012", "name": "张三"}, + {"id": "70123456789013", "name": "王小明"} + ], + "remark": "需要接机举牌" + }, + { + "id": "80012346", + "orderId": "60123456789012", + "direction": "DEPARTURE", + "transportType": "SELF_DRIVE", + "transportNo": null, + "carrier": null, + "departStation": null, + "arriveStation": null, + "departTime": null, + "arriveTime": null, + "selfDrivePeriod": "AFTERNOON", + "selfDriveEta": "2026-06-05T15:00:00", + "travelers": [ + {"id": "70123456789012", "name": "张三"} + ], + "remark": null + } + ], + "msg": "success" +} +``` + +#### 空数据响应 + +订单尚未登记任何大交通批次时: + +```json +{ "code": 200, "data": [], "msg": "success" } +``` + +#### 错误响应 + +订单不存在: + +```json +{ + "code": 581100, + "msg": "订单不存在", + "data": null +} +``` + +--- + +## 四、契约约束与正确调用方式 + +### ✅ 正确 / ❌ 错误调用对照 + +| 场景 | 请求 | +|------|------| +| ✅ 新路径 | `GET /v3/admin/order/60123456789012/transport-plan/list` | +| ❌ 旧路径(已下线 404) | `GET /v3/admin/order/60123456789012/transport-plans` | + +### 字段语义约束 + +- `direction` 取值固定两值: `ARRIVAL`(到达) / `DEPARTURE`(离开) +- `transportType` 三值: `FLIGHT` / `TRAIN` / `SELF_DRIVE` +- `SELF_DRIVE` 类型行: `transportNo` / `departTime` / `arriveTime` 必为 null;`selfDrivePeriod` 必有值 +- `FLIGHT` / `TRAIN` 类型行: `transportNo` / `departTime` / `arriveTime` 必有值;`selfDrivePeriod` / `selfDriveEta` 必为 null +- `travelers` 数组永远非空(后端保证),元素至少 1 个 + +--- + +## 五、数据库行为 + +**只读查询**,无写动作。 + +| 查询步骤 | 表 | 说明 | +|----------|-----|------| +| 1 | `order_transport_plan` | `WHERE order_id = ? AND deleted = 0`,主表批次记录 | +| 2 | LEFT JOIN `order_transport_plan_traveler` | `WHERE deleted = 0`,桥接表关联出行人 ID | +| 3 | JOIN `order_traveler` | `WHERE deleted = 0`,出行人姓名补全 | +| 4 | 内存装配 | 同一 plan 多 traveler 聚合为 `travelers: [{id, name}]` 数组 | + +排序: `ORDER BY direction ASC, depart_time ASC, id ASC` (ARRIVAL 先于 DEPARTURE,同方向按时间升序)。 + +--- + +## 六、边界行为 + +- 未登录 → 401(网关拦截) +- 订单不存在 → `581100` +- 订单存在但无任何批次 → 200 + `data: []`(不报错) +- 批次存在但桥接表全软删 → `travelers: []`(理论上不应出现,后端保证) +- 老数据(v2 历史 plan 无 `mode` 字段)→ 字段为 null,不异常 + +--- + +## 七、不影响范围(显式声明,帮前端 / QA 缩小排查面) + +- **仅影响**: 管理后台 F21 行程安排 Tab 「接送站」区块 - 大交通批次列表渲染 +- **零影响**: + - C 端订单详情 mp 接口(不查 plan 表,只查 arrival_plan) + - 订单创建 / 支付 / 退款主流程 + - 出行人模块(travelers list 仍走 `/admin/order/{id}/traveler/list`) + - Feign 内部接口 `GET /internal/order/orders/{orderId}/travelers`(回传 `transportPlanIds`,使用的是同表数据但聚合方向相反,不受路径变化影响) + +--- + +## 八、测试环境已验证 + +测试服 9443 网关 + 真 admin token round-trip 验证: + +``` +GET https://web.test.1814.love:9443/v3/admin/order/{orderId}/transport-plan/list + → 200 + data 数组 ✓ + → travelers 嵌套字段返回正确 (id+name) ✓ + → ARRIVAL/DEPARTURE 排序正确 ✓ + → 空订单返 data: [] ✓ +``` + +(commit `15e2c7eeb` 含 hotfix #2550 修复 conflict markers,4 接口一并 9443 真测过) + +--- + +## 九、相关历史 PR + +| PR | Issue | 说明 | 是否仍有效 | +|----|-------|------|------------| +| **本 PR #2544** | **#2521** | 路径破坏性迁移 `/transport-plans` → `/transport-plan/list` | ✅ 最新 | + +--- + +## 十、相关文档 + +- 关联 Issue: [wx/HL#2521](https://git.1814.love:8443/wx/HL/issues/2521) +- 关联 PR: [wx/HL#2544](https://git.1814.love:8443/wx/HL/pulls/2544) +- API 文档: `docs/order-v3/api/API-SPEC-V5.48.html` §2.6.1 +- 配套破坏性: 见 `18_#2522_transport-plan-add.md` / `18_#2523_transport-plan-edit.md` / `18_#2524_transport-plan-delete.md` diff --git a/changelogs-v2/2026-05/18_#2522_transport-plan-add.md b/changelogs-v2/2026-05/18_#2522_transport-plan-add.md new file mode 100644 index 0000000..d000998 --- /dev/null +++ b/changelogs-v2/2026-05/18_#2522_transport-plan-add.md @@ -0,0 +1,239 @@ +# 大交通批次新增: 接口路径破坏性迁移 + 4 个新错误码 + +> **存放目录**: 二期(v3,`order-v3` 标签)→ `changelogs-v2/2026-05/` +> +> **服务**: hl-order-v3 (端口 8084) +> **PR**: #2545 (commit `2c66137c7`) +> **Issue**: #2522 +> **日期**: 2026-05-18 +> **影响范围**: 管理后台「行程安排 - 接送站」区块 - 大交通批次新增弹窗 + +--- + +## ⚠️ 关键变化(破坏性 - 前端必同步) + +一行红字说清: +- **本次变了什么**: 新增大交通批次接口路径从 `/transport-plans` 改为 `/transport-plan/add`,并新增 4 个错误码 `581140`-`581143` +- **前端以前以为的是什么**: `POST /v3/admin/order/{id}/transport-plans` + Body `TransportPlanReqVO` +- **实际现在是什么**: `POST /v3/admin/order/{id}/transport-plan/add` + Body `TransportPlanReqVO`(字段不变) + +**配套破坏性**: §2.6 大交通批次模块 4 个接口路径风格 + 动词 + Method 全部统一迁移,见 #2521/#2523/#2524 changelog。 + +--- + +## 一、背景 + +V5.48 §2.6.2 将原 `POST /transport-plans` 拆为 `POST /transport-plan/add`,并补齐"字段组合 / 出行人合法性 / 时间顺序 / 同方向冲突"4 类业务校验错误码,与文档 §2.6 错误码段位 `581140`-`581143` 一一对应。 + +`orderId` 从 path 取,Body 不传;操作人从 JWT 派生(对齐 §2.6 通用约定)。 + +--- + +## 二、变更接口清单 + +| # | 接口 | 方法 | 路径 | 变更类型 | 说明 | +|---|------|------|------|----------|------| +| 1 | 大交通批次新增 | POST | `/v3/admin/order/{id}/transport-plan/add` | 路径迁移 + 错误码新增 | 旧 `/transport-plans` 下线;4 个业务错误码上线 | + +--- + +## 三、接口详情 + +### 1. 大交通批次新增 `POST /v3/admin/order/{id}/transport-plan/add` + +**VO**: `TransportPlanReqVO`(入参) / `TransportPlanVO`(出参) + +#### 入参 Body `TransportPlanReqVO` + +| 字段 | 类型 | 必填 | 约束 | 说明 | +|------|------|:----:|------|------| +| `direction` | String | ✅ | 枚举 | `ARRIVAL` / `DEPARTURE` | +| `transportType` | String | ✅ | 枚举 | `FLIGHT` / `TRAIN` / `SELF_DRIVE` | +| `transportNo` | String | 条件 | ≤50 | 航班号 / 车次号;`SELF_DRIVE` 时必须为 null | +| `carrier` | String | ❌ | ≤50 | 航司 / 铁路公司 | +| `departStation` | String | ❌ | ≤100 | 出发站 | +| `arriveStation` | String | ❌ | ≤100 | 到达站 | +| `departTime` | LocalDateTime | 条件 | - | `FLIGHT`/`TRAIN` 必填;`SELF_DRIVE` 必须为 null | +| `arriveTime` | LocalDateTime | 条件 | - | `FLIGHT`/`TRAIN` 必填;`SELF_DRIVE` 必须为 null;必须 ≥ `departTime` | +| `selfDrivePeriod` | String | 条件 | 枚举 | 仅 `SELF_DRIVE` 必填: `MORNING` / `AFTERNOON` / `EVENING` | +| `selfDriveEta` | LocalDateTime | ❌ | - | 仅 `SELF_DRIVE` 可选 | +| `travelerIds` | List | ✅ | size≥1 | 关联出行人 ID,至少 1 个 | +| `remark` | String | ❌ | ≤500 | 备注 | + +#### 出参 `Result` + +字段同 #2521 列表的元素结构(含 `travelers: [{id, name}]` 嵌套)。 + +#### 请求示例 + +```json +POST /v3/admin/order/60123456789012/transport-plan/add + +{ + "direction": "ARRIVAL", + "transportType": "FLIGHT", + "transportNo": "CA1234", + "carrier": "中国国际航空", + "departStation": "北京首都T3", + "arriveStation": "长春龙嘉", + "departTime": "2026-06-01T08:30:00", + "arriveTime": "2026-06-01T10:15:00", + "selfDrivePeriod": null, + "selfDriveEta": null, + "travelerIds": [70123456789012, 70123456789013], + "remark": "需要接机举牌" +} +``` + +#### 响应示例 + +```json +{ + "code": 200, + "data": { + "id": "80012345", + "orderId": "60123456789012", + "direction": "ARRIVAL", + "transportType": "FLIGHT", + "transportNo": "CA1234", + "carrier": "中国国际航空", + "departStation": "北京首都T3", + "arriveStation": "长春龙嘉", + "departTime": "2026-06-01T08:30:00", + "arriveTime": "2026-06-01T10:15:00", + "selfDrivePeriod": null, + "selfDriveEta": null, + "travelers": [ + {"id": "70123456789012", "name": "张三"}, + {"id": "70123456789013", "name": "王小明"} + ], + "remark": "需要接机举牌" + }, + "msg": "success" +} +``` + +#### 错误响应 + +| 错误码 | 含义 | 触发场景示例 | +|--------|------|--------------| +| `581140` | 大交通字段组合不合法 | `FLIGHT` 缺 `transportNo` / `SELF_DRIVE` 错带 `transportNo` 或 `departTime` | +| `581141` | travelerIds 含订单外的出行人 | 提交的 traveler ID 不属于当前订单或已软删 | +| `581142` | 出发时间晚于到达时间 | `departTime > arriveTime` | +| `581143` | 同方向同一出行人已在另一 plan | 张三已在 ARRIVAL plan A,再为 ARRIVAL plan B 提交张三 | +| `581100` | 订单不存在 | path 中 orderId 无效 | + +示例 `581140`: + +```json +{ "code": 581140, "msg": "大交通字段组合不合法: FLIGHT 类型必须填 transportNo", "data": null } +``` + +示例 `581143`: + +```json +{ "code": 581143, "msg": "同方向同一出行人已在另一 plan: 张三(70123456789012) 已存在 ARRIVAL 批次 80012340", "data": null } +``` + +--- + +## 四、契约约束与正确调用方式 + +### ✅ 正确 / ❌ 错误 payload 对照 + +| 场景 | payload 关键字段 | 结果 | +|------|------------------|------| +| ✅ FLIGHT 完整 | `transportType:FLIGHT, transportNo:CA1234, departTime/arriveTime 有值` | 201 | +| ✅ SELF_DRIVE 完整 | `transportType:SELF_DRIVE, transportNo:null, departTime:null, arriveTime:null, selfDrivePeriod:MORNING` | 201 | +| ❌ FLIGHT 缺航班号 | `transportType:FLIGHT, transportNo:null` | `581140` | +| ❌ SELF_DRIVE 带航班号 | `transportType:SELF_DRIVE, transportNo:CA1234` | `581140` | +| ❌ 时间倒挂 | `departTime:10:00, arriveTime:08:00` | `581142` | +| ❌ travelerIds 含他人 | `travelerIds:[别单出行人]` | `581141` | +| ❌ 同方向重复占用 | 同方向同 traveler 在另一 plan | `581143` | + +### 并发与幂等 + +- `@Idempotent(timeout = 3)`: 同 admin 3 秒内重复 POST 同请求体直接拿首次结果,防双击双提 +- `@Lock4j(keys = "#id")`: 同订单 30 秒锁,串行化 add/edit/delete,保证桥接表一致 + +### 同方向冲突规则 + +- 「同方向」= `direction` 相同的所有 plan(同 ARRIVAL 之间冲突,同 DEPARTURE 之间冲突) +- 「同一出行人」= 同一 `traveler_id` +- 不同方向不冲突(同一人可同时在 1 个 ARRIVAL plan + 1 个 DEPARTURE plan) +- 软删的 plan / 桥接行不参与冲突判定 + +--- + +## 五、数据库行为 + +单事务执行,失败整体回滚: + +| 步骤 | 表 | 动作 | +|------|-----|------| +| 1 | `order_transport_plan` | INSERT 新行,雪花 ID,`deleted=0`,`create_time`/`update_time` 自动填 | +| 2 | `order_transport_plan_traveler` | INSERT N 行(N = `travelerIds.size()`),每行 `{plan_id, traveler_id, deleted:0}` | + +校验顺序(任一失败即抛业务异常,事务回滚): + +1. 订单存在性 → `581100` +2. 字段组合校验(类型 × 字段必填矩阵)→ `581140` +3. 时间顺序校验 → `581142` +4. travelerIds 归属校验(SELECT order_traveler WHERE order_id 比对)→ `581141` +5. 同方向同 traveler 占用校验(SELECT bridge WHERE direction = ? AND traveler_id IN ?)→ `581143` + +--- + +## 六、边界行为 + +- 未登录 → 401(网关拦截) +- 订单不存在 → `581100` +- `travelerIds` 重复值 → 后端去重后再校验(不报错) +- `SELF_DRIVE` 仅传 `selfDrivePeriod` 不传 `selfDriveEta` → 通过(ETA 选填) +- 一次提交超过单订单合理上限(>10 plan)→ 不在此层拦截,业务上仅靠订单状态自然限制 + +--- + +## 七、不影响范围 + +- **仅影响**: 管理后台 F21 「接送站」新增弹窗 +- **零影响**: + - C 端订单详情 / 抵达计划(`order_arrival_plan` 表完全独立,见 detail 文档 §10.2 共存边界) + - 出行人模块自身 CRUD + - 订单状态机(plan 增删不直接变 `order_main.status`) + +--- + +## 八、测试环境已验证 + +测试服 9443 网关 + 真 admin token round-trip 验证: + +``` +POST https://web.test.1814.love:9443/v3/admin/order/{orderId}/transport-plan/add + → 200 + 返回新 plan(含 travelers 嵌套)✓ + → FLIGHT 缺 transportNo → 581140 ✓ + → SELF_DRIVE 错带 transportNo → 581140 ✓ + → departTime > arriveTime → 581142 ✓ + → travelerIds 含别单 → 581141 ✓ + → 同方向同 traveler 重复 → 581143 ✓ + → 重复 POST 3s 内同请求体 → 幂等返同结果 ✓ +``` + +(commit `15e2c7eeb` 含 hotfix #2550,4 接口一并 9443 真测过) + +--- + +## 九、相关历史 PR + +| PR | Issue | 说明 | 是否仍有效 | +|----|-------|------|------------| +| **本 PR #2545** | **#2522** | 路径迁移 + 错误码 581140-581143 上线 | ✅ 最新 | + +--- + +## 十、相关文档 + +- 关联 Issue: [wx/HL#2522](https://git.1814.love:8443/wx/HL/issues/2522) +- 关联 PR: [wx/HL#2545](https://git.1814.love:8443/wx/HL/pulls/2545) +- API 文档: `docs/order-v3/api/API-SPEC-V5.48.html` §2.6.2 +- 配套破坏性: 见 `18_#2521_transport-plan-list-path.md` / `18_#2523_transport-plan-edit.md` / `18_#2524_transport-plan-delete.md` diff --git a/changelogs-v2/2026-05/18_#2523_transport-plan-edit.md b/changelogs-v2/2026-05/18_#2523_transport-plan-edit.md new file mode 100644 index 0000000..1fb39b0 --- /dev/null +++ b/changelogs-v2/2026-05/18_#2523_transport-plan-edit.md @@ -0,0 +1,234 @@ +# 大交通批次编辑: Method 破坏性变更 (PUT → POST) + 错误码 581144 + +> **存放目录**: 二期(v3,`order-v3` 标签)→ `changelogs-v2/2026-05/` +> +> **服务**: hl-order-v3 (端口 8084) +> **PR**: #2546 (commit `1106b9d7f`) + hotfix #2550 (commit `15e2c7eeb` 修 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` + +字段同 #2521 列表元素结构。 + +#### 请求示例 + +```json +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": "改时间,加一人" +} +``` + +#### 响应示例 + +```json +{ + "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`: + +```json +{ "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 归属校验): + +1. plan 存在性 + 归属 → `581144` +2. 订单存在性 → `581100` +3. 字段组合 → `581140` +4. 时间顺序 → `581142` +5. travelerIds 归属 → `581141` +6. 同方向同 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](https://git.1814.love:8443/wx/HL/issues/2523) +- 关联 PR: [wx/HL#2546](https://git.1814.love:8443/wx/HL/pulls/2546) +- Hotfix PR: [wx/HL#2550](https://git.1814.love:8443/wx/HL/pulls/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` diff --git a/changelogs-v2/2026-05/18_#2524_transport-plan-delete.md b/changelogs-v2/2026-05/18_#2524_transport-plan-delete.md new file mode 100644 index 0000000..106104c --- /dev/null +++ b/changelogs-v2/2026-05/18_#2524_transport-plan-delete.md @@ -0,0 +1,187 @@ +# 大交通批次软删: Method 破坏性变更 (DELETE → POST) + 桥接表级联软删 + +> **存放目录**: 二期(v3,`order-v3` 标签)→ `changelogs-v2/2026-05/` +> +> **服务**: hl-order-v3 (端口 8084) +> **PR**: #2547 (commit `68887aba0`) +> **Issue**: #2524 +> **日期**: 2026-05-18 +> **影响范围**: 管理后台「行程安排 - 接送站」区块 - 大交通批次删除 + +--- + +## ⚠️ 关键变化(破坏性 - 前端必同步) + +一行红字说清: +- **本次变了什么**: 删除大交通批次接口 **Method 从 `DELETE` 改为 `POST`** + 路径子段从 `/transport-plans/{planId}` 改为 `/transport-plan/{planId}/delete` +- **前端以前以为的是什么**: `DELETE /v3/admin/order/{id}/transport-plans/{planId}` +- **实际现在是什么**: `POST /v3/admin/order/{id}/transport-plan/{planId}/delete` + +**Method 变化提醒**: `axios.delete(...)` 必须改 `axios.post(...)`;部分浏览器/代理对 `DELETE` 体行为不一致,统一 POST 后规避。 + +**配套破坏性**: §2.6 大交通批次模块 4 个接口路径风格 + 动词 + Method 全部统一迁移,见 #2521/#2522/#2523。 + +--- + +## 一、背景 + +V5.48 §2.6.4 与 §2.6.3 编辑接口同步,统一 POST + 动词路径风格(对齐订单核心模块、出行人模块写动作)。 + +软删行为: 主表 + 桥接表**同事务级联软删**,均置 `deleted=1`,不丢历史,可被审计回溯。 + +--- + +## 二、变更接口清单 + +| # | 接口 | 方法 | 路径 | 变更类型 | 说明 | +|---|------|------|------|----------|------| +| 1 | 大交通批次软删 | POST | `/v3/admin/order/{id}/transport-plan/{planId}/delete` | Method + 路径双破坏 | 旧 `DELETE /transport-plans/{planId}` 下线 | + +--- + +## 三、接口详情 + +### 1. 大交通批次软删 `POST /v3/admin/order/{id}/transport-plan/{planId}/delete` + +#### 入参 + +| 字段 | 位置 | 类型 | 必填 | 说明 | +|------|------|------|:----:|------| +| `id` | Path | Long | ✅ | 订单 ID | +| `planId` | Path | Long | ✅ | 批次 ID | + +> 无 Body。 + +#### 出参 `Result` + +| 字段 | 类型 | 说明 | +|------|------|------| +| `data` | Boolean | 始终 `true`(失败走错误码,不会返 `false`)| + +#### 请求示例 + +``` +POST /v3/admin/order/60123456789012/transport-plan/80012345/delete +``` + +#### 响应示例 + +```json +{ + "code": 200, + "data": true, + "msg": "success" +} +``` + +#### 错误响应 + +| 错误码 | 含义 | 触发场景 | +|--------|------|----------| +| `581100` | 订单不存在 | path 中 orderId 无效 | +| `581121` | plan 不属于该订单 | path orderId 与 plan.order_id 不一致 / planId 不存在 / 已软删 | + +> **错误码段位说明**: 本接口复用 §2 模块 baseline `581121`(plan 归属校验),而非 §2.6 add/edit 新增的 `581144`,这是 V5.48 §2.6.4 文档的有意约定(删除场景与新增/编辑的归属校验语义区分)。前端可对 `581121` / `581144` 做同样的"批次不存在"提示。 + +示例 `581121`: + +```json +{ "code": 581121, "msg": "大交通批次不属于该订单: planId=80099999", "data": null } +``` + +--- + +## 四、契约约束与正确调用方式 + +### ✅ 正确 / ❌ 错误调用对照 + +| 场景 | 请求 | 结果 | +|------|------|------| +| ✅ 新调用 | `POST /transport-plan/80012345/delete` | 200 + `data:true` | +| ❌ 沿用旧 DELETE | `DELETE /transport-plans/80012345` | 404 / 405 | +| ❌ planId 跨单 | path orderId=A,planId 属于 B | `581121` | +| ❌ 重复 delete | 已 `deleted=1` 的 plan 再 delete | `581121`(被软删的 plan 视同不存在) | + +### 幂等语义 + +- `@Idempotent(timeout = 3)`: 同 admin 3 秒内重复 POST 同 path 直接拿首次结果,防双击重复 +- `@Lock4j(keys = "#id")`: 同订单 30 秒锁,串行化 add/edit/delete +- 即使去掉幂等保护,二次 delete 也会因 `581121`(已软删视同不存在)被业务层正确拒绝,不会出现"软删后再软删"的脏数据 + +--- + +## 五、数据库行为 + +单事务执行,失败整体回滚: + +| 步骤 | 表 | 动作 | +|------|-----|------| +| 1 | `order_transport_plan` | UPDATE `deleted=1` WHERE `id = ? AND order_id = ? AND deleted = 0`,`update_time` 自动刷新 | +| 2 | `order_transport_plan_traveler` | UPDATE `deleted=1` WHERE `plan_id = ?`(级联软删所有桥接行) | + +> **级联软删而非物理删**: 桥接表保留 `deleted=1` 历史行,审计能查"曾经哪些出行人在该 plan",支持事后回溯。与 edit 接口的「全量重建桥接表用物理 DELETE」语义有意不同 — edit 重建后旧关联失去业务意义,delete 软删后旧关联是审计证据。 + +校验顺序: + +1. 订单存在性 → `581100` +2. plan 归属 + 未软删 → `581121`(若 UPDATE 影响 0 行表示 plan 不存在或已删 或 order_id 不匹配) + +--- + +## 六、边界行为 + +- 未登录 → 401(网关拦截) +- 订单不存在 → `581100` +- planId 不存在 / 已软删 / 跨单 → `581121`(用 UPDATE WHERE 复合条件一次判定,影响行数=0 即归一处理) +- 软删后,#2521 list 接口该 plan 不再返回(因 WHERE deleted=0 过滤) +- 软删后,该 plan 占用的"同方向同 traveler"释放,该出行人可在同方向 add 新 plan(`581143` 判定 WHERE deleted=0) +- 老数据 `order_transport_plan_traveler` 历史无 `deleted` 字段 → schema 已保证字段存在(V5.48 配套迁移已完成) + +--- + +## 七、不影响范围 + +- **仅影响**: 管理后台 F21 「接送站」删除按钮 +- **零影响**: + - C 端订单详情 / mp 抵达计划接口 + - 出行人模块本身(traveler 行不动,只动桥接关联) + - 订单状态机 + - 已签合同的 PDF 内容(合同生成时是快照,不会回溯查 plan) + +--- + +## 八、测试环境已验证 + +测试服 9443 网关 + 真 admin token round-trip 验证: + +``` +POST https://web.test.1814.love:9443/v3/admin/order/{orderId}/transport-plan/{planId}/delete + → 200 + data:true ✓ + → DB 验证 order_transport_plan.deleted=1 ✓ + → DB 验证 order_transport_plan_traveler.deleted=1 (级联) ✓ + → planId 不存在 → 581121 ✓ + → planId 跨单 → 581121 ✓ + → 重复 delete → 581121 (软删后视同不存在) ✓ + → delete 后 list 不再返回该 plan ✓ + → delete 后 add 同方向同 traveler 成功 (581143 占用释放) ✓ + → 3s 内重复 POST → 幂等返同结果 ✓ +``` + +(commit `15e2c7eeb` 含 hotfix #2550,4 接口一并 9443 真测过) + +--- + +## 九、相关历史 PR + +| PR | Issue | 说明 | 是否仍有效 | +|----|-------|------|------------| +| **本 PR #2547** | **#2524** | Method DELETE→POST + 路径迁移 + 桥接表级联软删 | ✅ 最新 | + +--- + +## 十、相关文档 + +- 关联 Issue: [wx/HL#2524](https://git.1814.love:8443/wx/HL/issues/2524) +- 关联 PR: [wx/HL#2547](https://git.1814.love:8443/wx/HL/pulls/2547) +- API 文档: `docs/order-v3/api/API-SPEC-V5.48.html` §2.6.4 +- 配套破坏性: 见 `18_#2521_transport-plan-list-path.md` / `18_#2522_transport-plan-add.md` / `18_#2523_transport-plan-edit.md`