diff --git a/changelogs/2026-04/2026-04-17_daily-mileage-refactor.md b/changelogs/2026-04/2026-04-17_daily-mileage-refactor.md new file mode 100644 index 0000000..c92a0ed --- /dev/null +++ b/changelogs/2026-04/2026-04-17_daily-mileage-refactor.md @@ -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` | 是 | `@NotEmpty`,**最多 30 天**(`@Size(max=30)`) | 按天分组的节点列表 | + +### `days[]`:`Day`(每日行程) + +| 字段 | 类型 | 必填 | 约束 | 说明 | +|------|------|------|------|------| +| `dayNumber` | `Integer` | 是 | ≥1 | 天序号(1,2,3...),与响应中的 `dayNumber` 对应 | +| `nodes` | `List` | 是 | `@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>` + +| 字段 | 类型 | 说明 | +|------|------|------| +| `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。 + +前端测试前请确认该服务已重启。