10 KiB
fix(product-v2): 上架校验补齐订单详情依赖字段(7 类新校验) + 订单详情字段补齐
服务: hl-product-service-v2 (端口 8083) + hl-order-service-v2 (端口 8084) PR: #854 wx/HL#854 Issue: #846 Merge commit:
a77c4505日期: 2026-04-18 影响范围:
- 管理端【产品上架】预检 + 上架接口(校验规则收紧,可能 400)
- 小程序【订单详情】弹窗(住宿/用车/餐饮/领队 4 处补齐字段)
一、背景
排查发现产品 v2 上架前校验 ProductValidationService 原本只做 11 项基础字段校验,对**订单详情 6 个弹窗(门票/住宿/餐饮/用车/领队/摄影)**所需要的关键字段覆盖不全,导致:
- 产品能上架,但用户下单后订单详情字段空白、统计恒为 0、Feign 返回 404 降级
- 订金配置不完整,下单时
depositAmount落 null - GROUP 产品可能没班期或班期无领队,订单详情领队弹窗永远"未分配"
本 PR 同时修复:
- P0x3 + P1x4 = 7 类新增上架校验(收紧规则,老产品可能上不了架)
- 订单详情依赖的 Feign 快照字段补齐(5 项 VO 字段补强 + 1 个 internal 接口新增)
二、上架校验收紧(P0 + P1 共 7 条)
变更接口
| # | 接口 | 方法 | 路径 | 变更类型 |
|---|---|---|---|---|
| 1 | 上架预检 | GET | /admin/product/{id}/validate-publish |
返回 issues 列表新增 7 类错误提示 |
| 2 | 上架/下架切换 | POST | /admin/product/{id}/toggle-publish |
上架动作先跑 validatePublish,有 issues 则 400 拒绝 |
新增 7 条校验规则
| # | 等级 | 触发条件 | 错误提示文案(示例) |
|---|---|---|---|
| 1 | P0 | payment_type=DEPOSIT 时 depositAmount>0 和 depositRatio∈[1,99] 都没填 |
订金支付方式(payment_type=DEPOSIT)必须配置订金金额(>0)或订金比例(1-99) |
| 2 | P0 | payment_type=DEPOSIT 时 balance_due_days 为 null |
订金支付方式(payment_type=DEPOSIT)必须配置尾款到期天数 balance_due_days |
| 3 | P0 | 行程节点缺少 HOTEL 类型 |
缺少酒店节点,订单详情住宿弹窗将为空 |
| 3 | P0 | 行程节点缺少 RESTAURANT 类型 |
缺少餐饮节点,订单详情餐饮弹窗将为空 |
| 3 | P0 | 行程节点 SCENIC + ACTIVITY 合计 = 0 |
缺少景点/活动节点,订单详情门票/活动弹窗将为空 |
| 4 | P1 | SCENIC 节点 resourceId 为 null |
SCENIC 景点节点(nodeId=X 名称=Y)缺少 resourceId,订单门票弹窗将无法显示封面/原价 |
| 5 | P1 | tripNights ≥ 1 且 非自理早餐(breakfast ≠ SELF)天数占比 < 70% |
餐饮配置不足:行程共 N 晚,至少 M 天早餐应该非自理(SELF),当前仅 K 天非自理,订单详情'已含早餐数'统计将不准确 |
| 6 | P1 | product_route_info.vehicle_model_id 为 null |
路线总览未配置实际用车车型(product_route_info.vehicle_model_id),订单用车弹窗将无法展示车型信息 |
| 7 | P1 | product_type=GROUP 且 group_tour_batch 为空 |
GROUP(小蒙马)产品至少需要 1 个班期(group_tour_batch),否则订单领队弹窗将永远为空 |
| 7 | P1 | product_type=GROUP 某班期 group_batch_staff 中没有 staff_role=LEADER 成员 |
班期(batchNo=X batchId=Y)缺少 LEADER 角色成员,订单详情领队弹窗将显示'未分配' |
校验接口响应示例
成功(全部通过):
{
"code": 0,
"success": true,
"message": "操作成功",
"data": []
}
失败(上架预检返回多条 issues):
{
"code": 0,
"success": true,
"message": "操作成功",
"data": [
"订金支付方式(payment_type=DEPOSIT)必须配置订金金额(>0)或订金比例(1-99)",
"缺少酒店节点,订单详情住宿弹窗将为空",
"SCENIC 景点节点(nodeId=1234 名称=额尔古纳湿地)缺少 resourceId,订单门票弹窗将无法显示封面/原价",
"GROUP(小蒙马)产品至少需要 1 个班期(group_tour_batch),否则订单领队弹窗将永远为空"
]
}
toggle-publish 触发上架失败时(HTTP 仍 200,code 为业务错误码):
{
"code": 400,
"success": false,
"message": "上架前校验失败:缺少酒店节点...;路线总览未配置实际用车车型...",
"data": null
}
前端必须配合的改动
1. 产品编辑页「上架前」先调 /validate-publish 给出整改清单
原型建议:点"上架"按钮前先 GET /admin/product/{id}/validate-publish,拿到 data: [] 则允许上架,否则展示 issues 清单供运营整改后再试。
// 推荐流程
const res = await GET(`/admin/product/${id}/validate-publish`);
if (res.data.length === 0) {
await POST(`/admin/product/${id}/toggle-publish`, { remark: '...' });
} else {
// 弹出整改清单,每行是一条中文错误提示
showIssuesModal(res.data);
}
2. 展示多条错误
toggle-publish 失败时后端的 message 会把所有 issues 用 ; 拼接,前端可以按 ; 切分展示,或者优先用 validate-publish 拿结构化列表。
三、兼容性风险(前端 + 运营重点关注)
老产品可能上不了架
以下情况产品会被拒绝上架,需要运营先补数据:
| 场景 | 解决 |
|---|---|
| 产品只有 NOTE/CUSTOM/TRANSPORT/FREE 节点 | 至少补 1 个 SCENIC/ACTIVITY + 1 个 HOTEL + 1 个 RESTAURANT 节点 |
老 DEPOSIT 产品只填了 depositAmount/depositRatio 没填 balanceDueDays |
编辑页补填尾款到期天数 |
| 老 GROUP 产品没建班期 | 至少创建 1 个班期 |
| 老 GROUP 产品班期没绑定领队 | 每个班期至少加 1 个 staff_role=LEADER 的成员 |
| 所有行程早餐都是 SELF(自理) | 至少 70% 的天早餐改成非 SELF(如 HOTEL/CAMP/SPECIAL) |
| SCENIC 景点节点只填了名称没关联景区资源 | 编辑节点绑定 resourceId |
| 路线总览没选实际车型 | 路线总览页选车型 |
早餐 70% 校验严格度说明
- 5 天行程(
tripNights=4):至少⌈4 × 0.7⌉ = 3天早餐非 SELF - 6 天行程(
tripNights=5):至少⌈5 × 0.7⌉ = 4天早餐非 SELF - 7 天行程(
tripNights=6):至少⌈6 × 0.7⌉ = 5天早餐非 SELF(只能有 1 天 SELF)
前端表单要能保存 SELF/HOTEL/CAMP/SPECIAL 四种早餐类型,上架校验才能放行。
已上架产品不会受影响
只有在「上架」动作时(toggle-publish 切到 PUBLISHED)才校验,已经处于上架状态的老产品没动它,下架再上架才会触发。
四、订单详情字段补齐(小程序 MP 端)
变更接口
| # | 接口 | 方法 | 路径 | 变更类型 | 说明 |
|---|---|---|---|---|---|
| 1 | 订单详情 | GET | /mp/order/{orderId}/detail |
响应字段扩展 | 4 个弹窗 VO 字段补全,数据从产品快照取 |
具体定位到订单详情 VO 层次:
1) MpHotelItemVO(订单住宿弹窗项)新增字段
| 字段 | 类型 | 可空 | 说明 |
|---|---|---|---|
hotelType |
String | 是 | 酒店类型(5星/特色民宿/蒙古包等),资源服务降级时可能为 null |
2) MpVehicleDetailVO(订单用车弹窗)新增字段
| 字段 | 类型 | 可空 | 说明 |
|---|---|---|---|
coverUrl |
String | 是 | 车型封面图 URL(资源服务暂未提供,预留 null) |
seatCount |
Integer | 是 | 座位数(来自产品快照 vehicleModels.seatCount) |
vehicleCategory |
String | 是 | 车辆分类,字典 vehicle_type:CAR=小轿车 / SUV=越野车 / VAN=商务车 / BUS=大巴 / MINIBUS=中巴 |
driverYearsRequired |
String | 是 | 驾龄要求(资源服务暂未提供,预留 null) |
features |
List<String> | 是 | 配置/亮点列表(资源服务暂未提供,预留空数组) |
3) MpMealStatsVO.breakfastCount(订单餐饮统计)行为修复
旧行为:breakfastCount 永远返回 0,因为产品 v2 快照没下发 mealType 字段,订单 OrderMealAssignmentService 无法区分早午晚餐。
新行为:产品快照补齐 nodes[].mealType(BREAKFAST/LUNCH/DINNER) 后,订单端按餐别分桶计数,breakfastCount 真实反映"产品内置多少餐早餐"。前端展示文案"已含 N 早"不用改,数值正确即可。
4) GROUP 产品订单详情【领队/摄影】弹窗不再永远"未分配"
旧行为:订单服务调 productFeignClient.getBatchStaff(batchId) 永远 404,fallback 返空 list → 所有 GROUP 订单领队/摄影弹窗显示"未分配"。
新行为:产品服务新增了 GET /internal/product/schedule/{batchId}/staff internal endpoint(详情见后端 changelog),订单服务能拿到真实的班期成员,按 staffRole 过滤展示领队/摄影/司机。
前端展示建议
订单住宿弹窗
原型上要求在酒店名下显示【5 星】【特色民宿】【蒙古包】标签 → 直接用 hotelType,为 null 则不展示标签。
订单用车弹窗
原型字段对照:
- "车型名":
vehicleType - "座位数":
seatCount(null → "—") - "车辆类型":翻译
vehicleCategory字典(null → "—") - "驾龄要求":
driverYearsRequired(null → "—"或隐藏) - "车辆配置":
features(空数组 → 隐藏)
五、下单/报价逻辑
本 PR 不改下单/报价核心逻辑,只改:
- 上架阶段收紧校验(卡在
toggle-publish) - 订单详情
VO补字段(从已有产品快照多读几个字段)
C 端已下单的历史订单,订单详情读的是创建时序列化的 product_snapshot,老订单没有新字段,仍会是 null。仅新下的订单会有完整字段。
六、不受影响的接口
- 小程序首页/列表/产品详情:零影响
- 产品创建/编辑/复制/下架:零影响
- 产品报价
/admin/product/item/{id}/quote:零影响 - 价格日历
/admin/product/item/{id}/price-calendar/*:零影响
七、受影响服务 + 重启提示
本次需要同时重启 2 个服务:
hl-product-service-v2(8083):新增校验 + 新增 internal endpointhl-order-service-v2(8084):VO 字段扩展 + Feign 快照反序列化新字段
测试环境重启顺序:先 product-service-v2,再 order-service-v2(order 依赖 product 的 internal 接口)。