hl-api-changelog/changelogs/2026-04/2026-04-17_product-v2_daily-mileage-step5-validation.md

6.1 KiB

修复daily-mileage 节点过滤 & Step5 上架预检必填校验

服务: hl-product-service-v2 (端口 8083) PR: #754 Issue: #751 日期: 2026-04-17 影响范围: Step2 行程编排里程试算、Step5 补充信息上架预检


问题 1P1 阻塞Step2 行程编排「计算每日里程」接口 400

现象

前端在 Step2 行程编排点"下一步",后端对节点列表里 resourceId 为空的自由活动节点返回:

{
  "code": 400,
  "message": "days[0].nodes[1].resourceId: resourceId不能为空"
}

根因

DailyMileageCalcReqVO.NodeRef.resourceId 挂了 @NotNull,但业务上 FREE/NOTE/TRANSPORT/CUSTOM/PHOTOGRAPHY 等节点本就不需要 resourceId它们没有对应的景区/活动/酒店资源)。

修复

  • 移除 @NotNullrequired=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_typeSCENIC / ACTIVITY / HOTEL / RESTAURANT / SERVICE / TRANSPORT / FREE / CUSTOM / NOTE
days[].nodes[].resourceId Long 只在 SCENIC/ACTIVITY/HOTEL 时必填;其他节点允许为空

问题 3Step5 上架预检补必填校验

现象

产品 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。