diff --git a/changelogs/2026-04/2026-04-24_mp_favorite-footprint-fields.md b/changelogs/2026-04/2026-04-24_mp_favorite-footprint-fields.md new file mode 100644 index 0000000..e872284 --- /dev/null +++ b/changelogs/2026-04/2026-04-24_mp_favorite-footprint-fields.md @@ -0,0 +1,118 @@ +# mp 收藏 / 浏览足迹接口: 字段全回填 + 新增产品类型字段 + 修复浏览记录保存后查不出 + +> **服务**: hl-mp-service (端口 8085) +> **PR**: #1338 +> **Issue**: 无关联工单(Gitea API 异常未能建单,已对齐口头确认) +> **日期**: 2026-04-24 +> **影响范围**: 小程序 收藏列表 + 浏览足迹列表 + 浏览记录保存接口 + +--- + +## ⚠️ 关键变化 + +1. **前一版 `/mp/user/favorite` 和 `/mp/user/footprint` 的 `records` 中 `targetType=PRODUCT`(或 `resourceType=PRODUCT`)的行,`name`/`coverUrl`/`tags` 全是 `null`**。本版修复后全部正确回填。 +2. `FavoriteItemVO` 和 `FootprintItemVO` **新增** `productType` + `productTypeLabel` 两字段(仅 PRODUCT 类型有值,其它类型 `null`)。前端若展示收藏/足迹卡片带产品类型(核心/小蒙马/私人定制)角标,直接用 `productTypeLabel` 即可,不用自己查字典。 +3. 保存足迹接口 `POST /mp/user/footprint` **修复隐性失败**:原实现下,用户"清空浏览记录"后再次访问同一资源时,接口返 200 但 DB 实际未保存记录,导致"保存成功却查不出来"。现在会正确复活已软删的记录。 +4. `footprintId` / `favoriteId` 在响应 JSON 中**全栈改为 String 序列化**(此前是 number,19 位雪花 ID 会被 JS 精度截断)。前端若以字符串接收,无感知;若以 number 运算需调整。 + +--- + +## 一、背景 + +`MpProductFeignClient.getProductBrief` Feign 路径历史写错,指向 `/internal/product/batch-brief`(班期摘要接口,返回 `BatchBriefVO` 只有 batchId/batchCode/departureDate 等班期字段),**没有 name/coverUrl/tags**。导致 mp BFF `FavoriteAggregationService` 拿到空 list → 字段全 null。资源类型(SCENIC/RESTAURANT/ACTIVITY)的三个 brief 端点无此问题。 + +浏览记录的问题则是 `addFootprint` 服务方法缺软删复活逻辑:清空浏览走 `@TableLogic` 把 `deleted_at` 置 NOW,下次访问时 `getOne` 自动加 `WHERE deleted_at IS NULL` 查不到 existing,进入 `save` 分支后撞上 DB 唯一索引(不含 deleted_at)冲突被静默吞。 + +--- + +## 二、变更接口清单 + +| # | 接口 | 方法 | 路径 | 变更类型 | 说明 | +|---|------|------|------|----------|------| +| 1 | 收藏列表 | GET | `/mp/user/favorite` | 响应字段补全 + 新增 | PRODUCT 行 `name`/`coverUrl`/`tags` 不再为 null;新增 `productType` + `productTypeLabel` | +| 2 | 浏览足迹列表 | GET | `/mp/user/footprint` | 响应字段补全 + 新增 | 同上 | +| 3 | 保存浏览记录 | POST | `/mp/user/footprint` | 行为修复 | 清空浏览后再次访问同资源不再静默失败 | + +路径/方法/参数**无变化**。 + +--- + +## 三、字段变化详情 + +### 3.1 `/mp/user/favorite` 响应 `records[*]` 字段 + +| 字段 | 类型 | 之前 | 之后 | 备注 | +|------|------|------|------|------| +| `favoriteId` | String | String | String | 无变 | +| `targetType` | String | String | String | 无变,枚举 `PRODUCT` / `SCENIC` / `RESTAURANT` / `ACTIVITY` | +| `targetId` | String | String | String | 无变 | +| `name` | String | PRODUCT 时为 `null` | **全类型正确返回** | PRODUCT 取产品名称;其它类型取资源名称 | +| `coverUrl` | String | PRODUCT 时为 `null` | **全类型正确返回** | PRODUCT 取产品封面图;其它类型取资源封面图 | +| `tags` | List\ | PRODUCT 时为 `null` | **全类型正确返回** | 仅存在标签时有值 | +| `productType` | String | **不存在** | **新增** | 仅 PRODUCT 有值,枚举 `CORE` / `GROUP` / `CUSTOM`(字典 `product_type`) | +| `productTypeLabel` | String | **不存在** | **新增** | 仅 PRODUCT 有值,中文,如 `核心产品` / `小蒙马` / `私人定制` | +| `createdAt` | String | String | String | 无变 | + +### 3.2 `/mp/user/footprint` 响应 `records[*]` 字段 + +同 `/mp/user/favorite`,除主键字段叫 `footprintId`、业务类型字段叫 `resourceType`/`resourceId`。 + +### 3.3 `POST /mp/user/footprint` 响应 +响应结构未变(仍为 `Result` 或 `Result`,视既有契约)。仅内部实现行为修复。 + +--- + +## 四、示例 + +### 4.1 修复前(已观察实际响应) +```json +{ + "code": 200, + "data": { + "records": [ + { + "favoriteId": "2047265158492815361", + "targetType": "PRODUCT", + "targetId": "2044306857534636034", + "name": null, + "coverUrl": null, + "tags": null, + "createdAt": "2026-04-23 18:43:45" + } + ], + "total": 1 + } +} +``` + +### 4.2 修复后 +```json +{ + "code": 200, + "data": { + "records": [ + { + "favoriteId": "2047265158492815361", + "targetType": "PRODUCT", + "targetId": "2044306857534636034", + "name": "冻干粉发短信给", + "coverUrl": "https://hlgl-test.oss-cn-beijing.aliyuncs.com/xxx.jpg", + "tags": ["亲子", "研学", "露营"], + "productType": "GROUP", + "productTypeLabel": "小蒙马", + "createdAt": "2026-04-23 18:43:45" + } + ], + "total": 1 + } +} +``` + +--- + +## 五、前端建议 + +1. 卡片 UI 如展示产品类型角标(核心/小蒙马/定制),直接用 `productTypeLabel`,不用前端再拉字典翻译。 +2. `productType`/`productTypeLabel` 在非 PRODUCT 类型(SCENIC/RESTAURANT/ACTIVITY)上是 `null`,渲染需判空。 +3. 原本用 `name?.trim() || "加载中..."` 等兜底文案的地方可以简化;字段现在一定不为 null(除非产品已被删除,此时 brief 返空 → 仍为 null,需保留兜底)。 +4. `productTypeLabel` 来自字典 `product_type`,字典 value→label 映射:`CORE→核心产品`、`GROUP→小蒙马`、`CUSTOM→私人定制`。若字典中文名称后续调整,后端会自动跟随,前端无需改动。