feat(product-v2): 每日行程里程接口改为 POST 实时试算 (PR #744)
- GET /admin/product/item/{id}/daily-mileage 已删除
- 新增 POST /admin/product/item/{id}/daily-mileage
- 前端在 Step3 路线设计过程中可传当前编辑态节点顺序实时试算
- 不再依赖必须先保存 Step2 行程
- 破坏性变更,前端必须切换新接口
这个提交包含在:
父节点
f0f287105a
当前提交
d14c42fbe6
@ -0,0 +1,194 @@
|
||||
# 每日行程里程接口重构:GET 改 POST,前端传节点顺序实时试算
|
||||
|
||||
> **服务**: hl-product-service-v2 (端口 8083,经网关 8080)
|
||||
> **PR**: #744
|
||||
> **Issue**: #737
|
||||
> **提交**: `7143671a`
|
||||
> **日期**: 2026-04-17
|
||||
> **类型**: refactor (破坏性变更)
|
||||
> **影响范围**: 管理端产品编辑 Step3「路线设计」
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ 破坏性变更(Breaking Change)⚠️
|
||||
|
||||
**旧的 `GET /admin/product/item/{id}/daily-mileage` 接口已删除**,前端必须切换到新的 `POST` 接口,否则页面上的里程/时长数据将无法获取。
|
||||
|
||||
---
|
||||
|
||||
## 改动概述
|
||||
|
||||
每日行程距离/时长的计算接口,由"后端从 DB 读取已保存的节点"改为"前端把当前编辑态的节点顺序传过来",使得 **Step3 路线设计过程中即可实时试算,不再依赖必须先保存 Step2**。
|
||||
|
||||
---
|
||||
|
||||
## 使用场景变化
|
||||
|
||||
| 场景 | 旧方案 | 新方案 |
|
||||
|------|--------|--------|
|
||||
| 触发时机 | 必须先保存 Step2 行程,再点按钮查询 | Step3 路线设计过程中随时触发 |
|
||||
| 数据来源 | 后端从 DB 按产品 ID 读取已保存节点 | 前端将"编辑态"节点顺序传给后端 |
|
||||
| 前端体验 | 改了顺序必须保存才能看新距离 | 拖动节点后立即可试算 |
|
||||
|
||||
---
|
||||
|
||||
## 旧接口(已删除)
|
||||
|
||||
| 方法 | 路径 |
|
||||
|------|------|
|
||||
| ~~GET~~ | ~~`/admin/product/item/{id}/daily-mileage`~~ |
|
||||
|
||||
旧接口无请求体,后端从 DB 读取。**调用会返回 404/405,请立即切换**。
|
||||
|
||||
---
|
||||
|
||||
## 新接口
|
||||
|
||||
| 方法 | 路径 |
|
||||
|------|------|
|
||||
| POST | `/admin/product/item/{id}/daily-mileage` |
|
||||
|
||||
**路径参数**:
|
||||
- `id`:产品 ID(`Long`,Path Variable),保持与其他接口一致;当前后端不依赖它从 DB 读节点,仅用于日志/鉴权上下文
|
||||
|
||||
**请求体**:`DailyMileageCalcReqVO`
|
||||
|
||||
---
|
||||
|
||||
## 请求体字段表
|
||||
|
||||
### 顶层:`DailyMileageCalcReqVO`
|
||||
|
||||
| 字段 | 类型 | 必填 | 约束 | 说明 |
|
||||
|------|------|------|------|------|
|
||||
| `days` | `List<Day>` | 是 | `@NotEmpty`,**最多 30 天**(`@Size(max=30)`) | 按天分组的节点列表 |
|
||||
|
||||
### `days[]`:`Day`(每日行程)
|
||||
|
||||
| 字段 | 类型 | 必填 | 约束 | 说明 |
|
||||
|------|------|------|------|------|
|
||||
| `dayNumber` | `Integer` | 是 | ≥1 | 天序号(1,2,3...),与响应中的 `dayNumber` 对应 |
|
||||
| `nodes` | `List<Node>` | 是 | `@NotEmpty`,**单日最多 50 个**(`@Size(max=50)`) | 该天的节点列表,**按驾车顺序** |
|
||||
|
||||
### `days[].nodes[]`:`Node`(节点)
|
||||
|
||||
| 字段 | 类型 | 必填 | 说明 |
|
||||
|------|------|------|------|
|
||||
| `nodeType` | `String` | 是 | 节点类型,见下方「节点类型处理规则」 |
|
||||
| `resourceId` | `Long` | 是 | 资源 ID,对应资源服务主键 |
|
||||
|
||||
---
|
||||
|
||||
## 节点类型处理规则
|
||||
|
||||
**只有以下 5 种"有坐标"的节点会被计算距离**:
|
||||
|
||||
| nodeType | 说明 |
|
||||
|----------|------|
|
||||
| `SCENIC` | 景点 |
|
||||
| `ACTIVITY` | 活动 |
|
||||
| `HOTEL` | 酒店 |
|
||||
| `RESTAURANT` | 餐厅 |
|
||||
| `SERVICE` | 服务/服务区 |
|
||||
|
||||
**以下类型会被静默忽略**(传了也不报错,但不参与计算):
|
||||
|
||||
| nodeType | 说明 |
|
||||
|----------|------|
|
||||
| `TRANSPORT` | 交通段 |
|
||||
| `PHOTOGRAPHY` | 拍摄/打卡 |
|
||||
| `FREE` | 自由活动 |
|
||||
| `CUSTOM` | 自定义 |
|
||||
| `NOTE` | 备注 |
|
||||
|
||||
前端可以放心把整天的节点全部传过来,后端会自行过滤。
|
||||
|
||||
---
|
||||
|
||||
## 响应字段表
|
||||
|
||||
**响应结构不变**:`Result<List<DailyMileageVO>>`
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| `dayNumber` | `Integer` | 天序号,与请求中的 `dayNumber` 一一对应 |
|
||||
| `mileage` | `BigDecimal` | 里程,单位:**公里(km)**,保留 1 位小数 |
|
||||
| `duration` | `Integer` | 驾车时长,单位:**分钟(min)** |
|
||||
|
||||
---
|
||||
|
||||
## 请求示例
|
||||
|
||||
```http
|
||||
POST /admin/product/item/123/daily-mileage
|
||||
Content-Type: application/json
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"days": [
|
||||
{
|
||||
"dayNumber": 1,
|
||||
"nodes": [
|
||||
{ "nodeType": "SCENIC", "resourceId": 3001 },
|
||||
{ "nodeType": "ACTIVITY", "resourceId": 5001 },
|
||||
{ "nodeType": "RESTAURANT", "resourceId": 6001 },
|
||||
{ "nodeType": "HOTEL", "resourceId": 7001 }
|
||||
]
|
||||
},
|
||||
{
|
||||
"dayNumber": 2,
|
||||
"nodes": [
|
||||
{ "nodeType": "HOTEL", "resourceId": 7001 },
|
||||
{ "nodeType": "SCENIC", "resourceId": 3002 },
|
||||
{ "nodeType": "SERVICE", "resourceId": 8001 },
|
||||
{ "nodeType": "SCENIC", "resourceId": 3003 }
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 响应示例
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 0,
|
||||
"data": [
|
||||
{ "dayNumber": 1, "mileage": 52.3, "duration": 78 },
|
||||
{ "dayNumber": 2, "mileage": 143.7, "duration": 195 }
|
||||
],
|
||||
"msg": ""
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 前端使用建议(Step3 路线设计实时试算)
|
||||
|
||||
1. **触发时机**:用户在 Step3 拖动/增删节点后(推荐 debounce 300~500ms 再请求),或点击"重新计算里程"按钮
|
||||
2. **数据来源**:直接用前端当前"编辑态"的节点顺序组装请求体,**不需要先保存草稿**
|
||||
3. **渲染**:按 `dayNumber` 匹配回请求中的每一天,展示 `mileage` 公里 + `duration` 分钟
|
||||
4. **类型过滤**:无需前端过滤节点类型,整天节点直接传即可
|
||||
5. **空值处理**:如果某一天节点都没有坐标(例如全是 FREE/CUSTOM),后端会返回 `mileage=0, duration=0`
|
||||
|
||||
---
|
||||
|
||||
## 校验约束汇总
|
||||
|
||||
| 规则 | 违反时错误码 |
|
||||
|------|-------------|
|
||||
| `days` 不能为空 | 400 参数校验失败 |
|
||||
| `days` 最多 30 天 | 400 参数校验失败 |
|
||||
| `days[].nodes` 不能为空 | 400 参数校验失败 |
|
||||
| `days[].nodes` 单日最多 50 个 | 400 参数校验失败 |
|
||||
|
||||
---
|
||||
|
||||
## 需要重启的服务
|
||||
|
||||
**hl-product-service-v2**(端口 8083,经网关 8080)—— 通过 Deploy Panel 重新部署最新 jar。
|
||||
|
||||
前端测试前请确认该服务已重启。
|
||||
正在加载...
x
在新工单中引用
屏蔽一个用户