修 PR #1285 的 404 问题后,把管理后台交通信息这块接口契约完整梳理一遍,给前端迁移指南: - 订单交通批次 5 个 CRUD (/admin/order/{orderId}/transport 或 /arrival,两个前缀别名): 查全量 / 新增 / 全量替换 / 修改 / 删除 - 飞常准查询 7 个 (/admin/transport/flight/* + /train/*): 航班号/机场/城市 + 车次/站点/城市 - 重点标出前端之前用的 v1 遗留契约(legType/departureStation/travelDate)与新契约的字段对照 - 补 FlightVO/TrainVO 完整字段表 + 三种交通方式的请求示例 - 推荐 UX: 航班号输入后自动调飞常准回填时间站点,避免用户手填时分秒
387 行
15 KiB
Markdown
387 行
15 KiB
Markdown
# 管理后台 · 订单交通信息(接送站)5 个 CRUD + 飞常准查询 7 个
|
||
|
||
**日期**:2026-04-23
|
||
**影响**:**管理后台** 订单详情·行程安排·交通信息(接送站)板块
|
||
**PR**:#1285(/transport 路径别名)
|
||
**关联 Issue**:#1284(404 修复)
|
||
|
||
---
|
||
|
||
## 概述
|
||
|
||
统一管理后台「交通信息」板块的接口契约。**管理后台前端之前对接的是一份已废弃的 v1 小程序契约**(`legType / departureStation / travelDate / ...`),和后端现有新契约(`direction / departStation / departTime + arriveTime / ...`)完全不匹配,导致:
|
||
|
||
1. `POST /admin/order/{orderId}/transport` **404**(#1285 已修:Controller 同时支持 `/transport` 和 `/arrival` 两个前缀)
|
||
2. 字段反序列化不上 → **400 `方向不能为空`、`出行人列表不能为空`**
|
||
|
||
本次梳理:管理后台前端请**完全对齐小程序端 `/mp/order/{orderId}/arrival` 的 DTO 结构**(字段名、必填规则都一样,仅路径前缀不同)。同时后端已开放 `/admin/transport/*` 共 7 个飞常准查询接口,供前端自动补全航班/车次时刻。
|
||
|
||
---
|
||
|
||
## 一、订单交通批次 5 个 CRUD
|
||
|
||
### 基础信息
|
||
|
||
- **鉴权**:Admin Bearer token
|
||
- **创建者**:`creatorType=ADMIN`(定制师代录;C 端小程序同样接口 `creatorType=USER`)
|
||
- **DTO**:后端 `ArrivalPlanSaveReqVO`(和小程序 `MpArrivalPlanSaveReqVO` 完全一致,字段名一一对应)
|
||
|
||
### 路径表
|
||
|
||
**URL 支持两个前缀别名(PR #1285)**,任选其一,推荐 `/transport`(与 DB 表 `order_transport_plan` 对齐):
|
||
|
||
| 功能 | 方法 | 路径 |
|
||
|---|---|---|
|
||
| 查全量批次 | GET | `/admin/order/{orderId}/transport` 或 `/admin/order/{orderId}/arrival` |
|
||
| 新增单批次 | POST | `/admin/order/{orderId}/transport` 或 `/admin/order/{orderId}/arrival` |
|
||
| 全量替换(多批次一次性提交) | POST | `/admin/order/{orderId}/transport/batch` 或 `/admin/order/{orderId}/arrival/batch` |
|
||
| 修改单批次 | PUT | `/admin/order/{orderId}/transport/plan/{planId}` 或 `/admin/order/{orderId}/arrival/plan/{planId}` |
|
||
| 删除单批次 | DELETE | `/admin/order/{orderId}/transport/plan/{planId}` 或 `/admin/order/{orderId}/arrival/plan/{planId}` |
|
||
|
||
---
|
||
|
||
### 1. 查全量批次
|
||
|
||
```
|
||
GET /admin/order/{orderId}/transport
|
||
```
|
||
|
||
#### 入参
|
||
|
||
| 参数 | 位置 | 类型 | 必填 |
|
||
|---|---|---|---|
|
||
| orderId | Path | Long | ✅ |
|
||
|
||
#### 出参 `Result<ArrivalListRespVO>`
|
||
|
||
| 字段 | 类型 | 说明 |
|
||
|---|---|---|
|
||
| orderId | Long | 订单ID |
|
||
| arrivals | `List<ArrivalPlanRespVO>` | 到达批次(接站) |
|
||
| departures | `List<ArrivalPlanRespVO>` | 离开批次(送站) |
|
||
|
||
`ArrivalPlanRespVO` 字段结构见第 2 节出参。
|
||
|
||
---
|
||
|
||
### 2. 新增单批次
|
||
|
||
```
|
||
POST /admin/order/{orderId}/transport
|
||
```
|
||
|
||
**创建者类型**:`creatorType=ADMIN`。
|
||
|
||
#### 入参
|
||
|
||
| 参数 | 位置 | 类型 | 必填 |
|
||
|---|---|---|---|
|
||
| orderId | Path | Long | ✅ |
|
||
|
||
**Body `ArrivalPlanSaveReqVO`**(完整字段表):
|
||
|
||
| 字段 | 类型 | 必填规则 | 约束 | 说明 |
|
||
|---|---|---|---|---|
|
||
| **direction** | String | ✅ 始终必填 | - | 方向。`ARRIVAL`=到达/接站;`DEPARTURE`=离开/送站。字典 `transport_direction` |
|
||
| **transportType** | String | ✅ 始终必填 | - | 交通方式。`FLIGHT`=飞机,`TRAIN`=火车,`SELF_DRIVE`=自驾。字典 `transport_type`(本模块子集) |
|
||
| transportNo | String | FLIGHT/TRAIN 必填;SELF_DRIVE 不填 | ≤32 | 航班号/车次号,如 `CA1234` / `G71` |
|
||
| carrier | String | 可选 | ≤64 | 航司/铁路公司,如 `中国国航` |
|
||
| departStation | String | DEPARTURE+FLIGHT/TRAIN 必填;其他场景可选 | ≤64 | 出发站/机场 |
|
||
| arriveStation | String | ARRIVAL+FLIGHT/TRAIN 必填;其他场景可选 | ≤64 | 到达站/机场 |
|
||
| departTime | LocalDateTime | DEPARTURE+FLIGHT/TRAIN 必填;其他场景可选 | - | 出发时间,`yyyy-MM-dd HH:mm:ss` |
|
||
| arriveTime | LocalDateTime | ARRIVAL+FLIGHT/TRAIN 必填;其他场景可选 | - | 到达时间,`yyyy-MM-dd HH:mm:ss` |
|
||
| selfDrivePeriod | String | SELF_DRIVE 必填 | - | 自驾时段:`MORNING` / `AFTERNOON` / `EVENING`。字典 `self_drive_period` |
|
||
| selfDriveEta | LocalDateTime | 可选 | - | 自驾预计到达精确时间 |
|
||
| remark | String | 可选 | ≤255 | 用户备注 |
|
||
| **travelerIds** | `List<Long>` | ✅ 始终非空 | 至少 1 人 | 本批次涉及的出行人 ID 列表;每个 ID **必须属于本订单** |
|
||
|
||
> ⚠️ **字段间联动**:字段必填规则由 Service 层按 `transportType + direction` 组合校验,不是单注解表达式。前端请按上表规则做前置校验,否则会 500。
|
||
|
||
#### 出参 `Result<ArrivalPlanRespVO>`
|
||
|
||
| 字段 | 类型 | 说明 |
|
||
|---|---|---|
|
||
| planId | Long | 批次ID |
|
||
| orderId | Long | 订单ID |
|
||
| direction | String | 方向 |
|
||
| directionLabel | String | 方向中文标签 |
|
||
| transportType | String | 交通方式 |
|
||
| transportTypeLabel | String | 交通方式中文标签 |
|
||
| transportNo | String | 航班号/车次号 |
|
||
| carrier | String | 航司/铁路公司 |
|
||
| departStation | String | 出发站 |
|
||
| arriveStation | String | 到达站 |
|
||
| departTime | LocalDateTime | 出发时间 |
|
||
| arriveTime | LocalDateTime | 到达时间 |
|
||
| selfDrivePeriod | String | 自驾时段 |
|
||
| selfDrivePeriodLabel | String | 自驾时段中文标签 |
|
||
| selfDriveEta | LocalDateTime | 自驾预计到达 |
|
||
| remark | String | 备注 |
|
||
| creatorType | String | `USER` / `ADMIN` |
|
||
| createTime | LocalDateTime | 创建时间 |
|
||
| travelers | `List<ArrivalPlanTravelerSimpleVO>` | 本批次涉及的出行人 |
|
||
|
||
**`ArrivalPlanTravelerSimpleVO`**:
|
||
|
||
| 字段 | 类型 | 说明 |
|
||
|---|---|---|
|
||
| travelerId | Long | 出行人 ID |
|
||
| name | String | 姓名 |
|
||
| travelerType | String | `ADULT` / `CHILD` / `YOUNG_CHILD` / `BABY` |
|
||
|
||
#### 请求示例(接站 ARRIVAL)
|
||
|
||
```json
|
||
{
|
||
"direction": "ARRIVAL",
|
||
"transportType": "FLIGHT",
|
||
"transportNo": "CA1234",
|
||
"carrier": "中国国航",
|
||
"departStation": "上海虹桥T2",
|
||
"arriveStation": "海拉尔东山",
|
||
"departTime": "2026-07-10 08:30:00",
|
||
"arriveTime": "2026-07-10 11:00:00",
|
||
"remark": "带小孩,需要婴儿座椅",
|
||
"travelerIds": [10001, 10002]
|
||
}
|
||
```
|
||
|
||
#### 请求示例(送站 DEPARTURE)
|
||
|
||
```json
|
||
{
|
||
"direction": "DEPARTURE",
|
||
"transportType": "FLIGHT",
|
||
"transportNo": "CA5678",
|
||
"carrier": "中国国航",
|
||
"departStation": "海拉尔东山",
|
||
"arriveStation": "上海虹桥T2",
|
||
"departTime": "2026-07-15 14:00:00",
|
||
"arriveTime": "2026-07-15 17:00:00",
|
||
"travelerIds": [10001, 10002]
|
||
}
|
||
```
|
||
|
||
#### 请求示例(自驾 SELF_DRIVE)
|
||
|
||
```json
|
||
{
|
||
"direction": "ARRIVAL",
|
||
"transportType": "SELF_DRIVE",
|
||
"selfDrivePeriod": "AFTERNOON",
|
||
"selfDriveEta": "2026-07-10 15:30:00",
|
||
"remark": "自驾从漠河过来",
|
||
"travelerIds": [10001]
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
### 3. 全量替换(多批次一次性提交)
|
||
|
||
```
|
||
POST /admin/order/{orderId}/transport/batch
|
||
```
|
||
|
||
先软删当前所有批次,再按请求体批量创建。空数组 `[]` = 清空订单全部交通批次。
|
||
|
||
#### 入参
|
||
|
||
| 参数 | 位置 | 类型 | 必填 |
|
||
|---|---|---|---|
|
||
| orderId | Path | Long | ✅ |
|
||
|
||
Body:`List<ArrivalPlanSaveReqVO>`(每项结构同第 2 节)。
|
||
|
||
#### 出参
|
||
|
||
`Result<ArrivalListRespVO>`,结构同第 1 节。
|
||
|
||
#### 使用场景
|
||
|
||
管理后台「一次性代录全部交通」按钮。例如定制师拿到客户的去程和回程航班信息,一次性把 2 条(或多条,如一家分两批)全部提交。
|
||
|
||
---
|
||
|
||
### 4. 修改单批次
|
||
|
||
```
|
||
PUT /admin/order/{orderId}/transport/plan/{planId}
|
||
```
|
||
|
||
**覆盖语义**:按字段全量覆盖,包括 `travelerIds`(替换旧关联)。
|
||
|
||
#### 入参
|
||
|
||
| 参数 | 位置 | 类型 | 必填 |
|
||
|---|---|---|---|
|
||
| orderId | Path | Long | ✅ |
|
||
| planId | Path | Long | ✅ |
|
||
|
||
Body:`ArrivalPlanSaveReqVO`(结构同第 2 节)。
|
||
|
||
#### 出参
|
||
|
||
`Result<ArrivalPlanRespVO>`。
|
||
|
||
---
|
||
|
||
### 5. 删除单批次
|
||
|
||
```
|
||
DELETE /admin/order/{orderId}/transport/plan/{planId}
|
||
```
|
||
|
||
**级联**:软删关联的出行人绑定行。
|
||
|
||
#### 入参
|
||
|
||
| 参数 | 位置 | 类型 | 必填 |
|
||
|---|---|---|---|
|
||
| orderId | Path | Long | ✅ |
|
||
| planId | Path | Long | ✅ |
|
||
|
||
#### 出参
|
||
|
||
`Result<Void>`。
|
||
|
||
---
|
||
|
||
## 二、航班/火车查询 7 个(`/admin/transport/*`)
|
||
|
||
数据源:飞常准官方 API,`resource-service` 侧做 Redis 缓存后透传。与小程序端 `/mp/transport/*` **底层共享同一批数据**。
|
||
|
||
**用途**:前端表单里用户输入航班号后,后端返回精确的起飞/到达时间、机场/航站楼等,**前端自动回填 `departTime / arriveTime / departStation / arriveStation`**,用户不必手动填时分秒。
|
||
|
||
### 路径表
|
||
|
||
| 功能 | 路径 | 核心入参 |
|
||
|---|---|---|
|
||
| 按航班号查航班 | `GET /admin/transport/flight/by-flight-no` | `flightNo`、`date` |
|
||
| 按机场三字码查航班 | `GET /admin/transport/flight/by-airport` | `dep`、`arr`、`date` |
|
||
| 按城市三字码查航班 | `GET /admin/transport/flight/by-city` | `depCity`、`arrCity`、`date` |
|
||
| 按车次号查火车(含经停站) | `GET /admin/transport/train/by-train-no` | `trainNo`、`date` |
|
||
| 按车次+出发/到达站查火车 | `GET /admin/transport/train/by-train-no-stations` | `trainNo`、`dep`、`arr`、`date` |
|
||
| 按出发/到达站查火车 | `GET /admin/transport/train/by-station` | `dep`、`arr`、`date` |
|
||
| 按出发/到达城市查火车 | `GET /admin/transport/train/by-city` | `depCity`、`arrCity`、`date` |
|
||
|
||
> ⚠️ 注意:admin 端路径风格和 mp 端**略有差异**(admin 端按航班号的叫 `/flight/by-flight-no`,mp 端叫 `/flight`;按车次号的叫 `/train/by-train-no`,mp 端叫 `/train`)。其他 5 个路径完全一致。
|
||
|
||
### 航班返回 `FlightVO` 字段
|
||
|
||
| 字段 | 类型 | 说明 |
|
||
|---|---|---|
|
||
| flightNo | String | 航班号 |
|
||
| airline | String | 航空公司名称 |
|
||
| category | String | `国内` / `国际` / `地区` |
|
||
| departAirportCode | String | 出发机场三字码 |
|
||
| arriveAirportCode | String | 到达机场三字码 |
|
||
| departAirport | String | 出发机场名称 |
|
||
| arriveAirport | String | 到达机场名称 |
|
||
| departCity | String | 出发城市 |
|
||
| arriveCity | String | 到达城市 |
|
||
| planDepartTime | String | 计划起飞时间 |
|
||
| planArriveTime | String | 计划到达时间 |
|
||
| actualDepartTime | String | 实际起飞时间(未起飞为 null) |
|
||
| actualArriveTime | String | 实际到达时间(未到达为 null) |
|
||
| status | String | `计划` / `起飞` / `到达` / `延误` / `取消` / `返航` / `备降` 等 |
|
||
| stopFlag | String | `0`=不经停,`1`=经停 1 次,`n`=经停 n 次 |
|
||
| shareFlag | String | `0`=否,`1`=是(共享航班) |
|
||
| shareFlightNo | String | 共享航班号 |
|
||
| departTerminal | String | 出发航站楼 |
|
||
| arriveTerminal | String | 到达航站楼 |
|
||
| boardGate | String | 登机口 |
|
||
| arriveExit | String | 到达出口 |
|
||
| departTimezone | String | 出发时区偏移(秒) |
|
||
| arriveTimezone | String | 到达时区偏移(秒) |
|
||
|
||
### 火车返回 `TrainVO` 字段
|
||
|
||
| 字段 | 类型 | 说明 |
|
||
|---|---|---|
|
||
| trainNo | String | 车次 |
|
||
| departStation | String | 出发车站 |
|
||
| arriveStation | String | 到达车站 |
|
||
| shutdown | String | `0`=否,`1`=停运 |
|
||
| planDepartTime | String | 计划出发时间 |
|
||
| planArriveTime | String | 计划到达时间 |
|
||
| estimatedDepartTime | String | 预计出发时间(仅 `by-train-no-stations` 返回) |
|
||
| estimatedArriveTime | String | 预计到达时间(仅 `by-train-no-stations` 返回) |
|
||
| actualDepartTime | String | 实际出发时间(仅 `by-train-no-stations` 返回) |
|
||
| actualArriveTime | String | 实际到达时间(仅 `by-train-no-stations` 返回) |
|
||
| departStatus | String | 出发状态:`计划`/`正点`/`晚点`/`出发`(仅 `by-train-no-stations`) |
|
||
| arriveStatus | String | 到达状态:`计划`/`正点`/`晚点`/`到达`(仅 `by-train-no-stations`) |
|
||
| duration | Integer | 运行时长(分钟) |
|
||
| stops | `List<TrainStopVO>` | 经停站列表(仅 `by-train-no` 返回) |
|
||
|
||
**`TrainStopVO`**:
|
||
|
||
| 字段 | 类型 | 说明 |
|
||
|---|---|---|
|
||
| stationName | String | 车站名称 |
|
||
| arriveTime | String | 到站时间 |
|
||
| departTime | String | 发车时间 |
|
||
|
||
### 边界
|
||
|
||
- 查不到数据(航班号无效/停运):返回 `data: []`
|
||
- 飞常准上游异常/超时:500,`message` 含上游错误码
|
||
- 日期格式:`yyyy-MM-dd`,格式错误返回 500
|
||
- 所有三字码不区分大小写(内部 `toUpperCase`)
|
||
|
||
---
|
||
|
||
## 三、字典依赖
|
||
|
||
| 字典类型 | 值 |
|
||
|---|---|
|
||
| `transport_direction` | `ARRIVAL` / `DEPARTURE` |
|
||
| `transport_type` | `FLIGHT` / `TRAIN` / `SELF_DRIVE`(本模块子集) |
|
||
| `self_drive_period` | `MORNING` / `AFTERNOON` / `EVENING` |
|
||
| `creator_type` | `USER` / `ADMIN` |
|
||
| `traveler_type` | `ADULT` / `CHILD` / `YOUNG_CHILD` / `BABY` |
|
||
|
||
所有 `xxxLabel` 字段由后端透传中文展示值,前端无需再次映射。
|
||
|
||
---
|
||
|
||
## 四、前端迁移指南(重要 ⚠️)
|
||
|
||
### 字段改名对照表
|
||
|
||
管理后台前端**之前在用 v1 小程序遗留契约**,请按下表迁移:
|
||
|
||
| 旧契约(v1 遗留) | 新契约(当前) | 说明 |
|
||
|---|---|---|
|
||
| `legType` (`OUTBOUND` / `RETURN`) | **`direction`** (`ARRIVAL` / `DEPARTURE`) | **字段名+值域都变了**。`OUTBOUND` 去程=客户到达目的地 → `ARRIVAL`;`RETURN` 回程=客户从目的地离开 → `DEPARTURE` |
|
||
| `departureStation` | **`departStation`** | 去掉 `ure` 词尾 |
|
||
| `arrivalStation` | **`arriveStation`** | 去掉 `al` 词尾 |
|
||
| `travelDate`(一个日期) | **`departTime` + `arriveTime`**(两个 LocalDateTime) | 拆成出发时间和到达时间,两者都要有时分秒 |
|
||
| 缺失 | **`travelerIds`(必填)** | 新增必填字段,至少 1 人,必须属于本订单 |
|
||
|
||
### 推荐 UX
|
||
|
||
用户填写交通信息时:
|
||
|
||
1. **方向**:单选 `接站 (ARRIVAL)` / `送站 (DEPARTURE)`
|
||
2. **交通方式**:单选 `飞机 (FLIGHT)` / `火车 (TRAIN)` / `自驾 (SELF_DRIVE)`
|
||
3. **航班号/车次号**:输入后立即调 `GET /admin/transport/flight/by-flight-no` 或 `GET /admin/transport/train/by-train-no`
|
||
4. 从返回列表让用户选具体班次,**自动回填** `departStation / arriveStation / departTime / arriveTime / carrier`
|
||
5. 自驾场景:改为输入 `selfDrivePeriod` + 可选 `selfDriveEta`
|
||
6. **出行人选择**:多选框,默认全选订单所有出行人(用户可取消部分),最少 1 人
|
||
7. 提交时按上表 DTO 校验格式
|
||
|
||
### 参考实现
|
||
|
||
小程序端已上线该 UI,表单交互和字段校验可**直接参考 `MpArrivalController` 的使用**(changelog: `2026-04-21_mp-order-arrival.md`)。唯一差别是路径前缀 `/admin` 和 `creatorType=ADMIN`。
|
||
|
||
---
|
||
|
||
## 五、关联 PR 与历史
|
||
|
||
- #1285(本次):Controller 同时支持 `/transport` 和 `/arrival` 两个前缀别名。修 404。
|
||
- #1262(2026-04-23):配房差价 execute 自动写 `order_surcharge` / `order_discount`(source=ROOM_UPGRADE),与本模块无关但同日相邻。
|
||
- 2026-04-21 MP 端 `/mp/order/{orderId}/arrival` 已上线,DTO 结构与本次 admin 端一致。
|
||
|
||
**待废弃**:`MpOrderTransportController`(`/mp/order/{orderId}/transport`,v1 遗留)已标 `@Deprecated`,请**不要再参考它的字段命名**。管理后台前端之前误以此为契约,本次必须迁移。
|