6.1 KiB
6.1 KiB
修复:daily-mileage 节点过滤 & Step5 上架预检必填校验
服务: hl-product-service-v2 (端口 8083) PR: #754 Issue: #751 日期: 2026-04-17 影响范围: Step2 行程编排里程试算、Step5 补充信息上架预检
问题 1(P1 阻塞):Step2 行程编排「计算每日里程」接口 400
现象
前端在 Step2 行程编排点"下一步",后端对节点列表里 resourceId 为空的自由活动节点返回:
{
"code": 400,
"message": "days[0].nodes[1].resourceId: resourceId不能为空"
}
根因
DailyMileageCalcReqVO.NodeRef.resourceId 挂了 @NotNull,但业务上 FREE/NOTE/TRANSPORT/CUSTOM/PHOTOGRAPHY 等节点本就不需要 resourceId(它们没有对应的景区/活动/酒店资源)。
修复
- 移除
@NotNull和required=true - 接口层面接受任何节点类型传入;Service 层按节点类型白名单自动过滤,不抛错
问题 2(业务规则):距离计算节点范围收窄 + 酒店去重
业务规则
每日驾车距离/时间计算只取:景区(SCENIC)+ 游玩项目(ACTIVITY)+ 当天第一个酒店(HOTEL)。
规则前后对比
| 节点类型 | 旧逻辑 | 新逻辑 |
|---|---|---|
| SCENIC | ✅ 参与 | ✅ 参与 |
| ACTIVITY | ✅ 参与 | ✅ 参与 |
| HOTEL | ✅ 全部参与 | ⚠️ 每天只保留 sortOrder 最小的那 1 个 |
| RESTAURANT | ✅ 参与 | ❌ 不参与 |
| SERVICE | ✅ 参与 | ❌ 不参与 |
| TRANSPORT / FREE / CUSTOM / NOTE / PHOTOGRAPHY | ❌ 不参与 | ❌ 不参与(无变化) |
举例:当天节点序列 景区1 → 餐厅 → 景区2 → 酒店A → 酒店B → 活动1
- 旧:6 点参与 →
dist(景1→餐→景2→店A→店B→活1) - 新:4 点参与 →
dist(景1→景2→店A→活1)(餐厅/酒店B 过滤)
影响接口
| 方法 | 路径 | 说明 |
|---|---|---|
| POST | /admin/product/item/{id}/daily-mileage |
Step3 实时试算(前端传节点序列) |
| GET | /admin/product/item/{id}/mileage |
总里程(基于保存后的节点) |
| GET | /admin/product/item/{id}/daily-mileage-saved |
基于保存后节点的按天里程 |
请求/响应契约
请求字段(无变化,仅校验收紧/放宽):
{
"days": [
{
"dayNumber": 1,
"nodes": [
{"nodeType": "SCENIC", "resourceId": 3001},
{"nodeType": "FREE"}, // resourceId 可空
{"nodeType": "RESTAURANT", "resourceId": 4001},
{"nodeType": "HOTEL", "resourceId": 5001},
{"nodeType": "HOTEL", "resourceId": 5002} // 同日第二个酒店 → 过滤
]
}
]
}
响应结构(无变化):
{
"code": 200,
"success": true,
"data": [
{"dayNumber": 1, "mileage": 42.35, "duration": 3600}
]
}
字段规范
| 字段 | 类型 | 说明 |
|---|---|---|
days[].dayNumber |
Integer | 天序号,从 1 开始 |
days[].nodes[].nodeType |
String | 节点类型(字典 product_node_type):SCENIC / ACTIVITY / HOTEL / RESTAURANT / SERVICE / TRANSPORT / FREE / CUSTOM / NOTE |
days[].nodes[].resourceId |
Long | 只在 SCENIC/ACTIVITY/HOTEL 时必填;其他节点允许为空 |
问题 3:Step5 上架预检补必填校验
现象
产品 Step5 补充信息里的「保险方案」「合同方案」「行程保障模板」即使没选,validatePublish 也不报 issue,导致产品能被提交上架但实际缺字段。
修复
在 GET /admin/product/item/{id}/validate-publish 的 L4 条款校验阶段,新增 3 条 issue:
| 条件 | 新增 issue 文案 |
|---|---|
insuranceNotice ∈ {INCLUDED, OPTIONAL} 且 insuranceSchemeId == null |
"保险方案未选择" |
contractSchemeId == null(不论保险类型) |
"合同方案未选择" |
guaranteeTemplateId == null(不论保险类型) |
"行程保障模板未选择" |
响应示例(缺字段时)
{
"code": 200,
"success": true,
"data": [
"保险方案未选择",
"合同方案未选择",
"行程保障模板未选择"
]
}
保存草稿接口保持宽松
PUT /admin/product/item/{id}/supplement不新增校验 —— 继续允许分步填写(空字段能保存)- 校验只发生在上架预检阶段
字典 insurance_notice
| Code | 说明 |
|---|---|
INCLUDED |
含保险(必选保险方案) |
OPTIONAL |
可选保险(必选保险方案) |
EXCLUDED |
不含保险(可不选保险方案) |
前端适配
Step2 行程编排
- 无需改动。前端传原样节点序列即可;后端自动按节点类型过滤不参与计算的节点。
- 旧行为:"自由活动"节点进入里程接口时 400 —— 已修复,不再会出现。
Step3 实时里程试算
- 如果前端之前有"过滤掉餐厅/服务"的代码,可以去除(后端已处理)。不去除也无影响(重复过滤 = 过滤)。
Step5 上架预检
- 前端调
validate-publish时,错误列表里会新出现如下 3 个文案,请前端错误映射里补上:"保险方案未选择""合同方案未选择""行程保障模板未选择"
- 表现为"点上架 → 后端返回这些 issue → 前端提示用户去 Step5 补充"。
单元测试
AmapDrivingServiceTest: 21 条(含"同日多酒店取首"、"餐厅/服务过滤"、"跨天多酒店各自取首"三条新增)ProductValidationServiceTest: 14 条(含 insuranceScheme / contractScheme / guaranteeTemplate 缺失各 1 条)
测试环境验证
api.test.1814.love:9443 实测通过:
| 用例 | 结果 |
|---|---|
POST /daily-mileage 混入 FREE 节点 |
200 ✅ |
POST /daily-mileage 同日 3 个 HOTEL |
200(按规则只取首个)✅ |
POST /daily-mileage 含 RESTAURANT+SERVICE |
200(已过滤)✅ |
GET /validate-publish 缺 3 字段 |
3 条 issue ✅ |
重启提示
需要重启 hl-product-service-v2(端口 8083 + 8183)—— 通过 Deploy Panel 重新部署最新 jar。