# product-v2: 节点 description 按产品线 seasons[0] 自动回填季节亮点 > **服务**: hl-product-service-v2 (端口 8083) + hl-resource-service (端口 8082) > **PR**: #2250 > **Issue**: #2243 > **日期**: 2026-05-14 > **影响范围**: 管理端 产品编辑 → 补充信息 → 产品卖点(节点 description 字段) --- ## ⚠️ 关键变化 紧接 #2229(景区季节)/#2232(游玩项目季节)/#2096(节点 description 默认从资源 highlights 回填)。 **之前**: 节点 description 为空时, 后端自动回填 = 资源**本体** `highlights`。 **现在**: 节点 description 为空时, 后端自动回填 = **`scenic_season/activity_season.highlights`(按产品线 seasons[0])→ 缺失再回落本体 `highlights`**。 **保持不变(独立快照)**: 节点 description **非空时不动**, 用户手填的卖点永远保留, 不会被后端覆盖。 --- ## 一、回填规则 ``` 节点 description = ( ( scenic_season.highlights WHERE scenic_id=X AND season_type=seasons[0] ) -- SCENIC 优先 || scenic_spot.highlights -- SCENIC 回落 || ( activity_season.highlights WHERE activity_id=X AND season_type=seasons[0] ) -- ACTIVITY 优先 || activity.highlights -- ACTIVITY 回落 || null ) ``` 季节匹配规则: 取**产品对应产品线** `productLine.seasons[0]`(首个季节), 与封面切换 #2229/#2234 行为一致。 **触发时机**: 仅在保存行程节点(`POST /admin/product/item/{id}/itinerary` 或同等接口)且节点 `description` 为空或空白字符时触发。已经填了内容的节点保存时不会被覆盖。 --- ## 二、影响接口 | # | 接口 | 方法 | 路径 | 变更类型 | 说明 | |---|------|------|------|----------|------| | 1 | 保存行程(快速理解节点) | POST/PUT | `/admin/product/item/{id}/itinerary` | 内部行为变更 | 入参/响应字段无变化 | | 2 | 产品详情 | GET | `/admin/product/{id}` | 数据来源变化 | `itineraryDays[].nodes[].description` 字段值来源由"本体 highlights"改为"季节 highlights 优先" | | 3 | 资源详情批量(internal) | POST | `/internal/resource/batch-details` | 入参加 `season` | 透明改动, 调用方为 product-v2 | 跨服务 DTO `ResourceDetailDTO` 新增字段 `seasonHighlights`(可为 null), 老消费者(Jackson 默认 ignore unknown)兼容, 不需要 redeploy。 --- ## 三、契约约束 - 前端无需任何改动 - 保存接口 payload 字段不变 - 详情响应字段不变(还是 `description`) - 前端在编辑页填的内容**永远不会**被后端覆盖, 不需要任何防御逻辑 --- ## 四、边界行为 | 场景 | 节点 description 自动回填值 | |------|----------------------------| | productLine.seasons = `["spring"]` + 节点指向有 spring 配置的景区 + description 空 | `scenic_season(spring).highlights` | | productLine.seasons = `["winter"]` + 节点指向**无 winter** 配置的景区 + description 空 | 回落 `scenic_spot.highlights`(本体) | | productLine.seasons = null/`[]` + description 空 | 回落本体 `highlights`(向前兼容, 与 #2096 行为一致) | | productLine.seasons = `["spring","autumn"]`(多季节) + description 空 | 取 seasons[0]=spring 匹配 | | ACTIVITY 节点 | 同款行为(`activity_season.highlights` 优先, 回落 `activity.highlights`) | | 节点 description **非空** | **不回填, 保留原值**(任何场景下都不覆盖) | | 资源详情查询失败/异常 | 静默降级(`seasonHighlights=null`), 走本体 highlights | --- ## 五、老数据 / 存量产品 **重要**: 此次改动**不会**主动遍历存量产品节点修改 description。 - 老产品节点 description 都是非空的(之前由 #2096 用本体 highlights 自动填了, 或用户手填的) → 保存时不会触发回填, **看起来"季节回落没生效"是正常的** - 想让某节点切换到季节亮点 → 编辑时**清空** description 文本框再保存即可 - 不接受"自动覆盖所有老节点"的需求(已与 wx 确认: 独立快照原则保护用户手填内容) --- ## 六、不影响范围 - **仅影响**: 管理端产品编辑保存时的节点 description 默认值 - **零影响**: - 小程序端 C 端展示(读 description 字段, 来源透明) - 订单创建/详情/退款 - 算价 / 价格日历 - 历史产品节点的现存 description 值 --- ## 七、测试环境已验证 ``` GET /admin/scenic/spots?page=1&pageSize=5&status=1 → 200 + seasons[] 字段 ✓ (PR #2229) GET /admin/activity/items?page=1&pageSize=5 → 200 + seasons[] 字段 ✓ (PR #2232) GET /admin/product/2045345825172639746 → 200 + itinerary.nodes[].description ✓ ``` 测试样本: 产品"游牧的森林-短途版"(`productLineId=2045004175602868226`, seasons=["autumn"])。3 个手填过 description 的 SCENIC 节点保持原值不变, 符合独立快照设计。 --- ## 八、相关历史 PR | PR | Issue | 说明 | 是否仍有效 | |----|-------|------|------------| | #2076 | #2068 | 资源 3 类 + 行程节点新增「图标」「卖点简介」2 字段 | ✅ 有效 | | #2091 | #2090 | 复用 description 删冗余 sellingPoint | ✅ 有效 | | #2096 | #2095 | 节点选资源时, 资源本体 highlights 默认带到节点 description | ✅ 有效, 被本 PR 增强 | | #2229 | #2227 | 产品设计选景区按产品线季节过滤 + 节点 seasons 标签 | ✅ 有效 | | #2234 | #2232 | 游玩项目同款季节过滤 + 标签 | ✅ 有效 | | **本 PR #2250** | **#2243** | 节点 description 默认值由"本体 highlights"升级为"季节 highlights 优先 + 回落本体" | ✅ 最新 | --- ## 九、相关文档 - Issue: [wx/HL#2243](https://git.1814.love:8443/wx/HL/issues/2243) - PR: [wx/HL#2250](https://git.1814.love:8443/wx/HL/pulls/2250)