219 行
6.7 KiB
Markdown
219 行
6.7 KiB
Markdown
# 小程序行程路线图接口 - `/mp/product/{productId}/route-map`
|
||
|
||
- **日期**: 2026-04-21
|
||
- **PR**: [#1098](https://git.1814.love:8443/wx/HL/pulls/1098) (Closes #1093) + 增量修复 [#1106](https://git.1814.love:8443/wx/HL/pulls/1106) (Closes #1104)
|
||
- **类型**: FEATURE(新增接口,免登录)
|
||
- **服务**: hl-product-service-v2(路由 `/mp/product/**`)
|
||
- **前端是否需要改动**: **是**(需新增接口调用 + 地图展示)
|
||
|
||
> ⚠️ **2026-04-21 增量修复(PR #1106)**:上线初期前端反馈 `origin`/`destination` 全部为空,根因是真实 `gather_place`/`dismissal_place` JSON 来自高德行政区 picker,只有 adcode 没有 `center` 字段。修复后通过 adcode 调高德 `/v3/config/district` 拿区县中心坐标(Redis 缓存 30 天)。**接口契约不变**,前端无需调整。
|
||
|
||
---
|
||
|
||
## 一、背景
|
||
|
||
小程序需要按产品 ID 拉取每天的「路线图」用于地图展示,所有节点必须带经纬度。无需登录(路线图是基础地理信息,浏览阶段即可看到)。
|
||
|
||
---
|
||
|
||
## 二、接口
|
||
|
||
| # | 方法 | 路径 | 鉴权 |
|
||
|---|------|------|------|
|
||
| 1 | GET | `/mp/product/{productId}/route-map` | 免登录(白名单) |
|
||
|
||
**Path 参数**
|
||
|
||
| 字段 | 类型 | 必填 | 说明 |
|
||
|------|------|------|------|
|
||
| `productId` | Long (字符串) | 是 | 产品 ID |
|
||
|
||
**无 Query/Body 参数**。
|
||
|
||
---
|
||
|
||
## 三、响应结构
|
||
|
||
### 顶层 `data: MpProductRouteMapVO`
|
||
|
||
| 字段 | 类型 | 说明 |
|
||
|------|------|------|
|
||
| `title` | String | 产品标题 |
|
||
| `days` | `MpProductRouteDayVO[]` | 每天的路线图,按 dayNumber 升序 |
|
||
|
||
### `MpProductRouteDayVO`
|
||
|
||
| 字段 | 类型 | 说明 |
|
||
|------|------|------|
|
||
| `dayNumber` | Integer | 第几天,从 1 开始 |
|
||
| `title` | String | 当天标题(如 "D1 成都-四姑娘山") |
|
||
| `color` | String / null | 当天主题色(后端不返回,固定 `null`,前端用调色板兜底) |
|
||
| `origin` | `MpRoutePointVO` / null | 当天起点;缺数据时为 null |
|
||
| `destination` | `MpRoutePointVO` / null | 当天终点;缺数据时为 null |
|
||
| `waypoints` | `MpRoutePointVO[]` | 当天途经点(按 sort_order 升序),可能空数组 |
|
||
| `scenicSpots` | `MpRouteScenicVO[]` | 当天景点(含封面图),可能空数组 |
|
||
|
||
### `MpRoutePointVO`(起点 / 终点 / 途经点)
|
||
|
||
| 字段 | 类型 | 说明 |
|
||
|------|------|------|
|
||
| `lng` | Number | 经度 |
|
||
| `lat` | Number | 纬度 |
|
||
| `name` | String | 名称 |
|
||
|
||
### `MpRouteScenicVO`(景点)
|
||
|
||
| 字段 | 类型 | 说明 |
|
||
|------|------|------|
|
||
| `lng` | Number | 经度 |
|
||
| `lat` | Number | 纬度 |
|
||
| `name` | String | 名称 |
|
||
| `coverUrl` | String / null | 封面图完整 URL,缺失为 null |
|
||
|
||
---
|
||
|
||
## 四、业务规则
|
||
|
||
- **起终点**:优先取行程当天「集合地 / 解散地」JSON 字段;若缺失,回退到该天 `product_route_point` 首/末
|
||
- **途经点**:取该天 `product_route_point` 全量按 `sort_order` 升序
|
||
- **景点**:取该天行程节点中 `nodeType ∈ {SCENIC, ACTIVITY}` 的资源,经纬度 + 封面图来自资源服务(景点表 / 活动表)
|
||
- **经纬度缺失节点直接过滤**(不会返回 `lng=0/lat=0` 或 `null` 坐标)—— 前端拿到的所有点都保证有有效坐标
|
||
- **未上架 / 已下架 / 不存在产品** → 业务码 `code=404, message="产品不存在或已下架"`(与现有 `/mp/product/{id}` 详情接口对齐)
|
||
- **景点封面图加载失败**(资源服务异常):景点列表降级为空数组,路线骨架仍正常返回(不阻断主流程)
|
||
|
||
---
|
||
|
||
## 五、响应示例
|
||
|
||
### PUBLISHED 产品(部分天有数据 + 部分天缺数据)
|
||
|
||
```json
|
||
{
|
||
"code": 200,
|
||
"success": true,
|
||
"data": {
|
||
"title": "川西亲子研学5日游(升级版)",
|
||
"days": [
|
||
{
|
||
"dayNumber": 1,
|
||
"title": "抵达成都",
|
||
"color": null,
|
||
"origin": { "lng": 103.947086, "lat": 30.578528, "name": "成都双流机场" },
|
||
"destination": { "lng": 104.061902, "lat": 30.673933, "name": "宽窄巷子" },
|
||
"waypoints": [
|
||
{ "lng": 104.061902, "lat": 30.673933, "name": "宽窄巷子" }
|
||
],
|
||
"scenicSpots": []
|
||
},
|
||
{
|
||
"dayNumber": 2,
|
||
"title": "成都-四姑娘山",
|
||
"color": null,
|
||
"origin": { "lng": 104.066301, "lat": 30.572961, "name": "成都" },
|
||
"destination": { "lng": 102.8987, "lat": 31.0827, "name": "四姑娘山" },
|
||
"waypoints": [
|
||
{ "lng": 102.8987, "lat": 31.0827, "name": "四姑娘山" }
|
||
],
|
||
"scenicSpots": []
|
||
},
|
||
{
|
||
"dayNumber": 3,
|
||
"title": "四姑娘山-丹巴",
|
||
"color": null,
|
||
"origin": null,
|
||
"destination": null,
|
||
"waypoints": [],
|
||
"scenicSpots": []
|
||
}
|
||
]
|
||
}
|
||
}
|
||
```
|
||
|
||
### 未上架 / 已下架 / 不存在
|
||
|
||
```json
|
||
{
|
||
"code": 404,
|
||
"success": false,
|
||
"message": "产品不存在或已下架",
|
||
"data": null
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 六、前端使用建议
|
||
|
||
### 调用示例
|
||
|
||
```ts
|
||
const res = await wx.request({
|
||
url: `https://api.test.1814.love:9443/mp/product/${productId}/route-map`,
|
||
method: 'GET'
|
||
// 不需要带 Authorization header
|
||
});
|
||
|
||
if (res.data.code !== 200) {
|
||
// 已下架 / 不存在
|
||
return;
|
||
}
|
||
|
||
const { title, days } = res.data.data;
|
||
```
|
||
|
||
### 渲染地图(高德/腾讯地图,伪代码)
|
||
|
||
```ts
|
||
const palette = ['#4285F4', '#EA4335', '#FBBC04', '#34A853', '#9C27B0'];
|
||
|
||
days.forEach((day, idx) => {
|
||
const color = day.color || palette[idx % palette.length];
|
||
|
||
// 折线:origin → waypoints → destination
|
||
const polylinePoints = [
|
||
day.origin,
|
||
...day.waypoints,
|
||
day.destination
|
||
].filter(Boolean); // 缺失的点跳过
|
||
|
||
drawPolyline(polylinePoints, color);
|
||
|
||
// 景点 marker
|
||
day.scenicSpots.forEach(spot => {
|
||
drawMarker(spot, { icon: spot.coverUrl, label: spot.name });
|
||
});
|
||
});
|
||
```
|
||
|
||
### ⚠️ 注意
|
||
|
||
- **所有节点都保证有 lng/lat**,前端无需再判空坐标
|
||
- **`origin` / `destination` 可能为 null**(当天数据不全),渲染折线时要过滤
|
||
- **`color` 后端固定返回 null**,前端按 dayNumber index 取调色板色
|
||
- **`scenicSpots[].coverUrl` 可能为 null**(景点未配封面),需有兜底图
|
||
- **`waypoints` / `scenicSpots` 可能空数组**
|
||
|
||
---
|
||
|
||
## 七、不兼容变更
|
||
|
||
**无**。新增接口,与既有接口无交集。
|
||
|
||
---
|
||
|
||
## 八、回归验证
|
||
|
||
```bash
|
||
# PUBLISHED 产品
|
||
curl -sk "https://api.test.1814.love:9443/mp/product/2044803580070703106/route-map" | jq .
|
||
|
||
# DRAFT 产品(应返回 404 业务码)
|
||
curl -sk "https://api.test.1814.love:9443/mp/product/2045018534152478721/route-map" | jq .
|
||
```
|
||
|
||
**预期**:
|
||
|
||
- PUBLISHED:`code=200`,`data.title` 非空,`data.days[]` 非空,每个有数据的 day 节点都带有效 lng/lat
|
||
- DRAFT:`code=404, message="产品不存在或已下架"`
|