From 21ce4af85e31abaf757273adb7e7cd19c5a1b2d9 Mon Sep 17 00:00:00 2001 From: API Changelog Bot Date: Sat, 23 May 2026 10:11:47 +0800 Subject: [PATCH] =?UTF-8?q?hotfix(product-v2):=20=E6=8B=96=E5=8A=A8?= =?UTF-8?q?=E8=A1=8C=E7=A8=8B=E5=AD=97=E6=AE=B5=E6=8A=B9=E5=B9=B3=E4=B8=BA?= =?UTF-8?q?=20null=20=E4=BF=AE=E5=A4=8D=20(PR=20#2933)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit P0 数据损坏 修复 + 真测通过, 前端无 API 变化无需改。 Co-Authored-By: Claude Opus 4.7 (1M context) --- ...3_hotfix_itinerary_reorder_wipes_fields.md | 84 +++++++++++++++++++ 1 file changed, 84 insertions(+) create mode 100644 changelogs/2026-05/23_hotfix_itinerary_reorder_wipes_fields.md diff --git a/changelogs/2026-05/23_hotfix_itinerary_reorder_wipes_fields.md b/changelogs/2026-05/23_hotfix_itinerary_reorder_wipes_fields.md new file mode 100644 index 0000000..48f45ba --- /dev/null +++ b/changelogs/2026-05/23_hotfix_itinerary_reorder_wipes_fields.md @@ -0,0 +1,84 @@ +# hotfix(product-v2): 拖动行程把所有天数据字段抹平为 null (P0 数据损坏) + +> **仓库**: HL (后端 hl-product-service-v2) +> **关联 PR/Issue**: PR #2933, Closes #2931 +> **日期**: 2026-05-23 +> **影响范围**: 行程天数拖拽重排接口 `/admin/product/item/{productId}/itinerary/reorder` +> **接收方**: mmg (前端) +> **前端**: **无 API 变化, 无需改前端** + +--- + +## 🚨 紧急修复说明 + +wx 反馈 (2026-05-23 09:50): 拖动产品行程天数重排后,**所有天的 dayTitle / description / coverImageUrl / 早晚餐 / 集合点 / 解散点 / 当日里程 等字段被抹空为 null**。 + +**P0 数据损坏**: 任何调用过 PR #2926 reorder 端点的产品,已经填好的行程内容全部被清空。day_id 关联的活动节点 (product_itinerary_node) 没丢, **只有 product_itinerary_day 主表的 IGNORED 策略字段被抹空**。 + +## 根因 + +`ProductItineraryDayDO` 大量字段标了 `@TableField(updateStrategy = FieldStrategy.IGNORED)` (Issue #2119 设计: 业务允许"清空字段")。PR #2926 reorderDays 实现里: + +```java +// 反例代码 (已修) +ProductItineraryDayDO up = new ProductItineraryDayDO(); // 空对象! +up.setDayId(d.getDayId()); +up.setDayNumber(-d.getDayNumber()); +negPhase.add(up); +dayService.updateBatch(negPhase); // IGNORED 策略下 null 字段会被写入! +``` + +`IGNORED` 策略下,**空对象的 null 字段会被 SQL UPDATE 写入 DB**,把用户填写的字段全部抹空。 + +## 修复 + +1. `ProductItineraryDayMapper` 新增 default `updateDayNumberById(dayId, dayNumber)`,用 `LambdaUpdateWrapperX.set` **只 update day_number 单列**,永不触碰其他字段 +2. `ProductItineraryService.reorderDays` Step1/Step2 改调 `dayService.updateDayNumberById`,不再走 `updateBatch(空对象集合)` +3. 新增回归测试 `reorderDays_doesNotWipeIgnoredFields` 守护 + +## API 行为变化 + +无! `/admin/product/item/{productId}/itinerary/reorder` 接口请求/响应字段完全一致,**前端无需做任何改动**。 + +修复前: 拖动后所有 IGNORED 字段被抹空 ❌ +修复后: 拖动后只改 day_number, 其他字段保留 ✅ + +--- + +## 🚨 前端需要做的事 + +**无需任何改动**。 + +可选: 通知 wx 数据库已损坏的产品(包括截图中的 ID=2053668473820622849)需要重新填写行程内容,**无法自动恢复**(binlog 时长有限)。 + +--- + +## 测试服真测记录 + +部署 product-v2 (rolling-deploy 2026-05-23 10:05) 后真测: + +| Step | 内容 | 结果 | +|------|------|------| +| 1 | DB 写测试字段: day_id=2043611284271677442 day_number=1 → day_title='TEST', description='应保留', cover='https://...', mileage=88.8 | ✅ 写入 | +| 2 | 调 reorder API 把 6 天全部倒序 (D1→6, D2→5, ...) | ✅ 200 重排成功 | +| 3 | 查 DB: day_id=2043611284271677442 跑到 day_number=6, **dayTitle/description/cover/mileage 完整保留** | ✅ 字段不丢 | +| 4 | 清测试数据 | ✅ | + +单测: hl-product-service-v2 `ProductItinerary*Test` **50/50 全绿** (含新增的 reorderDays_doesNotWipeIgnoredFields 回归测试 + 既有 ProductItineraryDOFieldStrategyTest 守护 IGNORED 策略) + +--- + +## 复盘 / 经验沉淀 + +**为什么 PR #2926 真测没捕获**: 当时真测脚本只验证 day_number 字段重排正确,**没验证其他字段不被改**。原 PR 真测产品的 dayTitle 本来就是 null, 无法暴露此 bug。 + +**今后真测要求**: 任何"写实体"的 PR 真测必须包含 `BEFORE / AFTER` 对照: 显式校验**未修改字段不被改**。已记入 MEMORY 经验。 + +--- + +## 联系人 + +后端: wx (呼籁旅行) +前端: mmg + +如有疑问可在 [#2931](https://git.1814.love:8443/wx/HL/issues/2931) 评论区留言。