# 出行人软删: DELETE → POST /traveler/{travelerId}/delete 路径迁移 + 真实业务化 > **服务**: hl-order-service-v3 (端口 8084 / 二期) > **PR**: #2537 > **Issue**: #2520 > **日期**: 2026-05-18 > **影响范围**: 管理后台 F27 改人数场景(临时取消同行 1 人) > **存放目录**: `changelogs-v2/2026-05/`(二期 v3 专属,带 -v2 后缀) > **部署 commit**: dev-v3 `34c294465` > **测试服已验证**: ✅(/@qa 通过 9443 网关 + 真 admin token round-trip) --- ## ⚠️ 关键变化 1. **破坏性路径 + 方法迁移**: - **方法**: DELETE → POST(对齐 v3 风格,所有写操作统一 POST) - **路径**: `/travelers/{travelerId}` → `/traveler/{travelerId}/delete`(对齐 V5.48 §2.4) 2. **从 Mock 占位变真实业务**: 4 道校验 + 软删 + 桥接表级联软删 + count-1 + status_log。 3. **新增 2 个错误码**: `581106`(已签合同禁删) / `581107`(最后 1 成人禁删)。 - ⚠️ 注意码段: **不是 581140/581141**,大交通段位 581120+ 已被占用,出行人主段位空挡是 581106/581107 紧贴主块(文档原稿写的 581119/581120 已被 TRAVELER_ID_CARD_DUPLICATE / TRANSPORT_PLAN_NOT_FOUND 占用)。 4. **加幂等 + 锁整事务**: `@Idempotent(3s) + @Lock4j(30s)`。 --- ## 一、背景 文档 V5.48 §2.4 定义: 软删除单行 `order_traveler`,同步: 1. UPDATE `order_main.Count` -1 2. UPDATE `order_transport_plan_traveler.deleted=1`(级联解除其在大交通桥接表的所有关联) 3. INSERT `order_status_log`,reason="删除出行人" 并有 2 条业务硬约束: - **已签电子合同(contract_status = SIGNED)的订单禁止删人**(合同已固定参与人,删除会破坏合同人员一致性) - **订单最后 1 位成人禁止删除**(无成人订单逻辑上无效,会影响保险 / 大交通 / 房型分配) --- ## 二、变更接口清单 | # | 接口 | 方法 | 老路径 | 新路径 | 变更类型 | |---|------|------|--------|--------|----------| | 1 | 出行人软删 | DELETE → **POST** | `/v3/admin/order/{id}/travelers/{travelerId}` | `/v3/admin/order/{id}/traveler/{travelerId}/delete` | 破坏性方法 + 路径迁移 + 真实业务化 | --- ## 三、接口详情 ### 1. 出行人软删 `POST /v3/admin/order/{id}/traveler/{travelerId}/delete` #### 入参 | 字段 | 位置 | 类型 | 必填 | 说明 | |------|------|------|------|------| | `id` | Path | Long | ✅ | 订单 ID | | `travelerId` | Path | Long | ✅ | 出行人 ID | (请求体: 无) #### 出参 `Result` ```json { "code": 200, "data": true, "msg": "success" } ``` #### 请求示例 ``` POST /v3/admin/order/60123456789012/traveler/70123456789013/delete Authorization: Bearer {admin_jwt} ``` #### 错误响应清单(本工单新增段位) | 错误码 | 含义 | |---|---| | `581100` | 出行人不存在 | | `581102` | 订单不存在,无法删除出行人 | | `581106` | **已签电子合同,禁止删除出行人**(本 PR 新增) | | `581107` | **出行人是订单最后 1 位成人,禁止删除**(本 PR 新增) | | `581110` | 出行人 ID 不属于该订单 | > 段位说明: 文档原稿 §2.4 期望 `581119 / 581120`,但 581119 已被 TRAVELER_ID_CARD_DUPLICATE 占用,581120 已被 TRANSPORT_PLAN_NOT_FOUND 占用。本 PR 落到出行人主段位空挡 **581106 / 581107**,紧贴主块,保留 581140-581144 给 §2.6 大交通后续工单。 错误响应体示例: ```json { "code": 581106, "msg": "已签电子合同,禁止删除出行人", "data": null } ``` --- ## 四、契约约束 ### 校验顺序(任一失败整事务回滚,4 道闸) 1. **订单存在性**(581102): 查 `order_main` by `id` 2. **出行人归属**(581110): 出行人的 `order_id` 必须等于 path `id`(防越权 / 错传) 3. **合同冻结**(581106): 订单 `contract_status = SIGNED` → 拒绝 4. **最后 1 成人**(581107): 当前操作是 ADULT 且订单未软删的 ADULT 行数 = 1 → 拒绝 ### 并发控制 - `@Idempotent(timeout=3s)`: 同 `(orderId, travelerId)` 3 秒重复提交直接拒绝(防快速双击) - `@Lock4j(expire=30000ms)`: 同订单 30 秒锁,与 batch-edit / add 互斥,防 count 字段 race --- ## 五、数据库行为 整事务: | 操作 | 表 | 说明 | |------|------|------| | UPDATE | `order_traveler` | `deleted=1` `deleted_at=NOW()` | | UPDATE | `order_transport_plan_traveler` | 该出行人所有未软删的桥接行 `deleted=1`(级联解除大交通关联) | | UPDATE | `order_main.Count` | -1(按 `travelerType` 选择: adultCount / childCount / youngChildCount / babyCount) | | INSERT | `order_status_log` | reason="删除出行人",flowStatus 不变(from == to) | --- ## 六、边界行为 - 未登录 → 401(网关拦截) - 订单不存在 → 581102 - 出行人不属于订单(越权) → 581110 - 合同 SIGNED → 581106(优先于人数校验) - 删最后 1 成人 → 581107(允许删 CHILD / YOUNG_CHILD / BABY 即使他们也是最后 1 个) - 出行人有大交通关联 → 自动级联软删桥接表,不阻断主流程 - 出行人已被软删过(重复删) → 581100(查不到行) --- ## 七、不影响范围 - **仅影响**: 管理后台 F27 改人数(删除)场景 - **零影响**: - 批量编辑 `/traveler/batch-edit`(#2518 独立接口) - 新增 `/traveler/add`(#2519 独立接口) - 列表 `/traveler/list`(只读) - 大交通错误码段位 581120-581129(保留) - 老路径 `DELETE /travelers/{travelerId}` 已下线,前端调用必须改用 `POST /traveler/{travelerId}/delete` --- ## 八、测试环境已验证 ``` POST https://web.test.1814.love:9443/v3/admin/order/60123456789012/traveler/70123456789013/delete Authorization: Bearer {admin_jwt} → 200 + data: true ✓ → order_traveler.deleted=1 ✓ → order_transport_plan_traveler 级联 deleted=1 ✓ → order_main.child_count -1 ✓ → order_status_log 新增 1 行 reason=删除出行人 ✓ 反例: - 合同 SIGNED 删人 → 581106 ✓ - 删最后 1 成人 → 581107 ✓ - travelerId 不属于该订单 → 581110 ✓ - DELETE 老方法老路径 → 404 / 405 ✓ ``` --- ## 九、相关历史 PR | PR | Issue | 说明 | 是否仍有效 | |----|-------|------|------------| | #1984 | - | [§2 traveler skeleton] 空骨架 Mock | ❌ 被本 PR 真实化覆盖 | | #2535 | #2518 | batch-edit 真实化(同期工单,共用 581100-581114) | ✅ 有效 | | #2536 | #2519 | add 真实化(同期工单,581115-581118) | ✅ 有效 | | **本 PR #2537** | **#2520** | delete 接通真实业务 + 方法+路径迁移 + 2 错误码(581106/581107) | ✅ 最新 | --- ## 十、相关文档 - API SPEC: `D:/work2/HL-v3/docs/order-v3/api/API-SPEC-V5.48.html` §2.4 - 关联 Issue: [wx/HL#2520](https://git.1814.love:8443/wx/HL/issues/2520) - 关联 PR: [wx/HL#2537](https://git.1814.love:8443/wx/HL/pulls/2537)