diff --git a/changelogs/2026-04/2026-04-23_mp-product-schedules-remain-fields.md b/changelogs/2026-04/2026-04-23_mp-product-schedules-remain-fields.md new file mode 100644 index 0000000..3e35fba --- /dev/null +++ b/changelogs/2026-04/2026-04-23_mp-product-schedules-remain-fields.md @@ -0,0 +1,106 @@ +# C 端班期: schedules 接口补 `remainParticipants` / `remainFamilies` 字段, 订单后不再缓存穿透 + +> **服务**: hl-product-service-v2 (端口 8083) +> **PR**: #1296 +> **Issue**: #1292 +> **日期**: 2026-04-23 +> **影响范围**: C 端小程序小蒙马(GROUP 类型)产品详情页"余多少家庭/余多少可报名"展示 + 下单后班期余量刷新 + +--- + +## ⚠️ 关键变化 + +1. **新增 2 个字段**: `GET /mp/product/{id}/schedules` 每个班期对象新增 `remainParticipants`(余可报名数, 人数口径) 和 `remainFamilies`(余家庭数, 房间口径) — 之前只返 `maxParticipants/enrolledCount/maxRooms/bookedRooms` 原始字段, 前端需要自己算差值。现在后端直接给聚合值。 +2. **缓存失效修复**: 之前下单成功后 5 分钟内重复调 schedules 返回旧 `enrolledCount`/`bookedRooms`(Redis 缓存未失效), 现在入团(enroll)/取消报名(unenroll)事务提交后立刻清 `mp:product:schedules:{productId}` 缓存, 前端下次 GET 立即拿到最新余量。 + +**前端影响**: 无破坏。旧前端忽略新字段继续工作; 新前端直接用 `remainParticipants` / `remainFamilies` 展示, 不需自己 max-used。 + +--- + +## 一、背景 + +原 `MpScheduleRespVO` 只返回 4 个库级原始字段, C 端小蒙马产品详情页"余多少家庭""余多少可报名"需前端 `max - used` 自算; 即使前端算了, 因 schedules 查询 Redis 缓存 5 分钟 TTL 且下单链路不清缓存, 下单成功后展示余量 5 分钟内仍是旧值。 + +--- + +## 二、变更接口清单 + +| # | 接口 | 方法 | 路径 | 变更类型 | 说明 | +|---|------|------|------|----------|------| +| 1 | 小程序查班期列表 | GET | `/mp/product/{id}/schedules` | 响应体新增字段 | `remainParticipants` + `remainFamilies` | + +内部接口行为变化(前端无感, 仅后端服务调用): +- `POST /internal/product/batch/{batchId}/enroll` — 成功后清 `mp:product:schedules:{productId}` 缓存 +- `POST /internal/product/batch/{batchId}/unenroll` — 同上 + +--- + +## 三、接口详情 + +### 1. 查询班期列表 `GET /mp/product/{id}/schedules` + +**VO**: `MpScheduleRespVO` + +#### 响应字段(只列新增) + +| 字段 | 类型 | 说明 | +|------|------|------| +| `remainParticipants` | Integer | **余可报名数**(人数口径) = `maxParticipants - enrolledCount`。`maxParticipants` 为 `null` 或 `0` 时返回 `null` 表示"不限人数"。 | +| `remainFamilies` | Integer | **余家庭数**(房间口径, 1 家庭 ≈ 1 房间起) = `maxRooms - bookedRooms`。`maxRooms` 为 `null` 或 `0` 时返回 `null` 表示"不限家庭"。 | + +#### 响应示例 + +```json +{ + "code": 200, + "message": "成功", + "data": [ + { + "batchId": 2042973109799215105, + "productId": 2042973108708696065, + "batchNo": "Q2026051101", + "batchName": "五一特别班", + "adultPrice": 2999.00, + "childPrice": 1599.00, + "departureDate": "2026-05-01", + "endDate": "2026-05-05", + "enrollmentDeadline": "2026-04-30", + "maxParticipants": 23, + "enrolledCount": 2, + "maxRooms": 19, + "bookedRooms": 1, + "remainParticipants": 21, + "remainFamilies": 18, + "batchStatus": "ENROLLING" + } + ] +} +``` + +"不限"场景示例: +```json +{ + "maxParticipants": null, + "enrolledCount": 0, + "maxRooms": 0, + "bookedRooms": 0, + "remainParticipants": null, + "remainFamilies": null +} +``` + +前端建议: 判空优先于展示。`null` 表示"不限/无上限"(对应 UI 可显示 "不限" 或隐藏)。 + +--- + +## 四、前端接入点 + +- **下单前**: 展示 `remainFamilies` 代替原本的 `maxRooms - bookedRooms` 自算逻辑 +- **下单后**: 无需改动逻辑。下单调用订单服务后, 再次 `GET /mp/product/{id}/schedules` 返回值已即时反映新余量(无需等 5 分钟缓存过期) +- **首次详情展示**: 若对应字段为 `null`, 当作"不限/无上限", UI 做相应文案或隐藏 + +--- + +## 五、不向后兼容风险 + +无。纯响应体新增字段, 未改任何现有字段类型/语义。