feat(product-v2): 行程操作日志展开节点/天/住宿具体变化 (PR #2511)

这个提交包含在:
API Changelog Bot 2026-05-18 13:28:03 +08:00
父节点 06c7a23261
当前提交 3c9cb3f89a

查看文件

@ -0,0 +1,69 @@
# product-v2: 行程操作日志展开节点/天/住宿具体变化
> **服务**: hl-product-service-v2 (端口 8083)
> **PR**: #2511
> **Issue**: #2510
> **日期**: 2026-05-18
> **影响范围**: STEP2 行程编辑操作日志 `changes` 字段输出形态
---
## mmg 必看 — 接口契约不变,changes 内部条数和内容形态变化
接口 URL / Method / 入参 / 出参字段类型**完全不变**。仅 `changes[]` 数组的条数和每条 `fieldLabel`/`oldValue`/`newValue` 内容形态优化。
### 修复前(聚合,看不出动了哪个节点)
```json
"changes": [
{
"fieldLabel": "活动节点列表",
"oldValue": "共 11 条",
"newValue": "共 8 条 (新增 0 / 删除 3)",
"changeType": "MODIFY"
}
]
```
用户原话:**"没法直观的发现变化应该写具体的节点变化"**。
### 修复后(展开,每个节点变化各一条)
```json
"changes": [
{ "fieldLabel": "删除 活动节点", "oldValue": "DAY 1 #3 中俄边境公路(卡线)", "newValue": null, "changeType": "DELETE" },
{ "fieldLabel": "删除 活动节点", "oldValue": "DAY 2 #1 雾凇画廊", "newValue": null, "changeType": "DELETE" },
{ "fieldLabel": "删除 活动节点", "oldValue": "DAY 4 #2 那达慕狂欢", "newValue": null, "changeType": "DELETE" },
{ "fieldLabel": "新增 活动节点", "oldValue": null, "newValue": "DAY 1 #5 海拉尔国家森林公园", "changeType": "ADD" },
{ "fieldLabel": "修改 活动节点", "oldValue": "DAY 1 #1 礼仪送机", "newValue": "DAY 1 #1 礼仪送机+迎宾", "changeType": "MODIFY" }
]
```
### 三个集合的 display 格式
| 集合 | display 格式 | 例 |
|---|---|---|
| 行程天 | `DAY X` | `DAY 1` |
| 活动节点 | `DAY X #序号 节点名称` | `DAY 1 #5 海拉尔国家森林公园` |
| 住宿 | `DAY X 档位N 酒店ID=...` | `DAY 1 档位1 酒店ID=2046123456789` |
### 上限保护
单次行程编辑变化 > 30 条时,前 30 条展开,剩余聚合成一条 `"其他 N 项变更未展开 (日志条数超上限 30)"`
---
## 服务端改动
- `EntityFieldDiffUtil.diffCollectionDetailed(...)`: 新方法,接受 `oldDisplay/newDisplay` 抽取器,把集合 diff 展开成 ADD/DELETE/MODIFY 各一条 FieldChange
- `ProductItineraryService.buildItineraryChanges`: 三集合都改用 detailed 版,提供 display 抽取器
- 由于 NodeItem/HotelItem VO 没 `dayId` 字段,用 `IdentityHashMap<NodeItem, Integer>` 在遍历 newDays 时建反向映射
- ProductItineraryServiceTest 17/17 全绿
---
## 部署
- 测试服: 已部署 (deploy task `e88d639e`)
- 正式服: 等 Deploy Panel /prod 触发
- nacos: 无配置变化