4.8 KiB
4.8 KiB
修复: 费用包含按名字去重 + name 字段填具体景区/活动/酒店名
类型: 后端 BUG 修复(PR #1568 follow-up) 关联: 工单 #1576 / PR #1577 日期: 2026-04-30 前端处理者: mmg 影响范围: 管理后台「产品编辑 → 补充信息 → 费用说明 →『根据行程自动生成』」按钮
背景
PR #1568 已修复「自动生成」漏景区/活动/酒店的问题(每节点 1 条 + 追加酒店),但用户使用后反馈 2 个新问题:
问题 1: 同名条目重复
同一酒店多天住就出现多次 — 产品 2045018534152478721「游牧的森林·南线经典」自动生成 35 条结果中:
- 「海拉尔洲季酒店」出现 11 次(11 天住同一酒店)
- 「呼伦贝尔香格里拉大酒店」出现 4 次
- 「额尔古纳玖成万豪水汇智能酒店」出现 4 次
- 27 条住宿真实只有 9 家不同酒店
问题 2: name 字段无信息量
三种类型(景区/活动/酒店)的 name 字段都填 feeType 标签 — 即:
- 6 条门票项
name都是"门票"(实际景区名是黑山头/白桦林景区/莫日格勒河等) - 27 条住宿项
name都是"住宿"(实际酒店名各不同)
前端中间 label 区域显示重复的 chip 文字,无信息量。
修复
hl-product-service-v2 ProductValidationService.feeDeductionPreview():
1. 按名字去重
LinkedHashSet<String> seenNodeNames = new LinkedHashSet<>();
for (ProductItineraryNodeDO node : sortedNodes) {
String nodeName = node.getNodeName();
if (nodeName == null || nodeName.isBlank()) continue;
if (!seenNodeNames.add(nodeName)) continue; // ← 按名字去重
result.add(...);
}
LinkedHashSet<String> seenHotelNames = new LinkedHashSet<>();
for (ProductDayHotelDO hotel : sortedHotels) {
String hotelName = hotel.getHotelName();
if (hotelName == null || hotelName.isBlank()) continue;
if (!seenHotelNames.add(hotelName)) continue;
result.add(...);
}
LinkedHashSet 保留首次出现(行程顺序里第一次见到该名字的那条)。
2. name 字段填具体名
| 字段 | 改前 | 改后 |
|---|---|---|
feeType |
门票/活动/住宿/餐饮/交通 | 不变(前端 chip 用) |
name |
同 feeType | 节点名/酒店名 |
sourceNodeName |
节点名/酒店名 | 不变(与 name 同值) |
icon |
null | 不变(PR #1573 行为) |
影响接口
| 方法 | 路径 | 改动 |
|---|---|---|
| GET | /admin/product/item/{id}/fee-deduction-preview |
数组长度变短(去重),每项 name 改为具体名,其他字段语义不变 |
请求签名完全不变,响应字段不变。
响应示例
GET /admin/product/item/2045018534152478721/fee-deduction-preview
{
"code": 200,
"data": [
/* 6 门票 */
{"feeType": "门票", "name": "黑山头", "source": "AUTO", "sourceNodeName": "黑山头", "icon": null},
{"feeType": "门票", "name": "白桦林景区", "source": "AUTO", "sourceNodeName": "白桦林景区", "icon": null},
/* ... 共 6 条 */
/* 2 活动 */
{"feeType": "活动", "name": "ATV全地形越野车穿越", "source": "AUTO", "sourceNodeName": "ATV全地形越野车穿越", "icon": null},
{"feeType": "活动", "name": "驯鹿拉车体验", "source": "AUTO", "sourceNodeName": "驯鹿拉车体验", "icon": null},
/* 9 住宿(去重后,不再有重复) */
{"feeType": "住宿", "name": "海拉尔洲季酒店", "source": "AUTO", "sourceNodeName": "海拉尔洲季酒店", "icon": null},
/* ... 共 9 条 */
]
}
旧返回 35 条,新返回 17 条(6 门票 + 2 活动 + 9 住宿)。
前端要做的改动
0 改动。前端模板 {{ item.name }} 直接渲染就显示具体名字了。
sourceNodeName 字段值与 name 同值,如果前端模板里有 ← {{ item.sourceNodeName }} 来源标签,会出现"门票 chip + 黑山头 + ← 黑山头"的视觉重复。前端可选:
- 保留来源标签作为视觉标识(用户已习惯)
- 或加
v-if="item.sourceNodeName !== item.name"隐藏冗余
测试覆盖
mvn test -pl hl-product-service-v2: 1045/1045 全绿。
新增 3 单测:
feeDeductionPreview_duplicateNodeNames_dedupedByName— 5 个同名 SCENIC 节点 → result.size=1feeDeductionPreview_sameHotelMultipleDays_dedupedByName— 11 个同酒店多天 → result.size=1feeDeductionPreview_nullOrBlankName_skipped— null/空名字跳过
测试服 round-trip(产品 2045018534152478721)实测:
- 总计 17 条(6 + 2 + 9)
- 全部
nameunique(去重生效) - 全部
name是具体名字(黑山头/ATV 全地形越野车穿越/海拉尔洲季酒店 等) - 全部
iconnull(PR #1573 行为不变)
部署状态
- ✅ Deploy Panel 已部署测试服 hl-product-service-v2 双实例(2026-04-30 16:56)
- 正式环境暂未发布(等 dev → main release PR)