diff --git a/changelogs/2026-05/16_feat_product_v2_snapshot_save_restore.md b/changelogs/2026-05/16_feat_product_v2_snapshot_save_restore.md new file mode 100644 index 0000000..2e8651c --- /dev/null +++ b/changelogs/2026-05/16_feat_product_v2_snapshot_save_restore.md @@ -0,0 +1,194 @@ +# product-v2: 产品全量快照 — 按需保存 + 一键还原(新功能) + +> **服务**: hl-product-service-v2 (端口 8083) +> **PR**: #2431 +> **Issue**: #2425 +> **日期**: 2026-05-16 +> **影响范围**: 产品编辑页右侧新增「版本管理」入口(保存/列表/还原/删除/标记永久) + +--- + +## ⚠️ 关键新功能(mmg 前端需新增 UI) + +产品支持「按需手动保存版本」+「一键还原到任意历史版本」。**这是个全新模块,前端需要新增 3 个组件**: + +1. **保存版本按钮**(产品编辑页右上角) +2. **版本列表页/抽屉**(点按钮看历史版本) +3. **还原对话框**(必填还原原因 ≥10 字符) + +--- + +## 一、设计要点 + +| 项 | 设计 | +|---|---| +| 触发方式 | **按需手动**(不是每次 EDIT 自动) | +| 存储位置 | 阿里云 OSS(`hlgl-test` bucket,prefix `hl-product-snapshots/`) | +| 压缩 | gzip(典型 40KB JSON 压到 8-10KB) | +| 版本上限 | 每产品最多 10 个;keep_forever 标记每产品最多 3 个 | +| 超阈值 | 自动删最老的非 keep_forever 快照 | +| 还原后 | 状态强制 DRAFT(避免与审批/上架冲突),保留 id/created_by/createdAt | +| 权限 | SUPER_ADMIN 或 CUSTOMIZER+owner(复用 P2 改价权限校验) | + +--- + +## 二、5 个新接口 + +### 1. 保存快照 +`POST /admin/product/item/{productId}/snapshots` + +入参 VO `ProductSnapshotSaveReqVO`: +| 字段 | 类型 | 必填 | 校验 | +|---|---|---|---| +| name | String | ✅ | ≤64 字符,同产品下唯一 | +| description | String | ❌ | ≤255 字符 | +| keepForever | Boolean | ❌ | 默认 false | + +出参:`Result` (snapshotId) + +**错误码**: +- `410120` SNAPSHOT_NOT_FOUND +- `410121` SNAPSHOT_INVALID_STATE(仅 DRAFT/COMPLETED 可保存) +- `410122` SNAPSHOT_NAME_DUPLICATE +- `410123` SNAPSHOT_SIZE_EXCEED(序列化 >1MB 拒绝) +- `410124` SNAPSHOT_KEEP_FOREVER_LIMIT(>3 个 keep_forever 拒绝) +- `410115` PRICE_EDIT_PERMISSION_DENIED(复用,非 SUPER_ADMIN/CUSTOMIZER+owner) +- `510100` SNAPSHOT_OSS_FAIL(OSS 服务端错误) + +### 2. 列表 +`GET /admin/product/item/{productId}/snapshots?page=1&size=20` + +出参(分页): +```json +{ + "code": 200, + "data": { + "list": [{ + "id": "...", + "productId": "...", + "name": "上架前最终版", + "description": "改价前备份", + "fileSize": 9876, + "keepForever": true, + "createdBy": "...", + "createdByName": "wangyu", + "createdAt": "2026-05-16 17:23:11" + }], + "total": 5, + "pageNum": 1, + "pageSize": 20 + } +} +``` + +### 3. 还原 +`POST /admin/product/item/{productId}/snapshots/{snapshotId}/restore` + +入参 VO `ProductSnapshotRestoreReqVO`: +| 字段 | 必填 | 校验 | +|---|---|---| +| reason | ✅ | ≥10 字符 | + +**还原后效果**: +- 产品全字段 + 所有子表(priceCalendars/days/dayNodes/dayHotels/feeItems/supplies/tiers/...)恢复到快照时点 +- 产品状态强制设回 `DRAFT` +- 产品 id / created_by / createdAt 保持不变 +- 自动写一条 `RESTORE` 操作日志,detail = `"还原至 {name} ({createdAt}), 原因: {reason}"` + +**错误码**: +- `410125` SNAPSHOT_RESTORE_PUBLISHED(已上架不能还原,必须先 UNPUBLISH) +- `410126` SNAPSHOT_RESTORE_HAS_ORDER(有报名班期不能还原) + +### 4. 删除 +`DELETE /admin/product/item/{productId}/snapshots/{snapshotId}` + +软删 MySQL + 异步删 OSS 对象(OSS 删失败 log.warn 不阻塞)。 + +### 5. 标记/取消永久保留 +`PUT /admin/product/item/{productId}/snapshots/{snapshotId}/keep-forever?keepForever=true` + +切换 keep_forever 标记。设 true 时校验 keep_forever 总数 < 3。 + +--- + +## 三、前端 UI 建议(mmg) + +### 保存按钮(编辑页右上角) + +```jsx + +``` + +保存对话框: +- 输入版本名(如"上架前最终版" / "调价前备份") +- 选填描述 +- 复选框「永久保留(不被阈值清理)」 + +### 版本列表(侧边抽屉) + +| 版本名 | 描述 | 大小 | 创建人 | 时间 | 操作 | +|---|---|---|---|---|---| +| 上架前最终版 📌 | 改价前备份 | 9.6 KB | 王宇 | 17:23 | 还原 / 删除 / 取消📌 | +| 改 6 月价之前 | — | 8.2 KB | 王宇 | 16:01 | 还原 / 删除 / 📌 | + +📌 = keep_forever 标记,不会被阈值清理删除。 + +### 还原对话框 + +``` +⚠️ 还原到「上架前最终版」(2026-05-16 17:23) + +还原后: +- 产品所有字段会回到该版本(包括价格、行程、备品等) +- 产品状态会强制设回"草稿" +- 操作日志会记录一条 RESTORE + +还原原因(必填,≥10 字): +[ ] + +[ 确认还原 ] [ 取消 ] +``` + +--- + +## 四、部署前置(运维必做) + +合并部署前 **nacos 必须配 OSS 凭据**,否则保存/还原接口返 `SNAPSHOT_OSS_FAIL`: + +```yaml +# hl-common-{dev,test,prod}.yml +product: + snapshot: + oss: + access-key-id: + access-key-secret: + bucket: hlgl-test # 或新建专用 bucket + endpoint: oss-cn-beijing.aliyuncs.com # 已有默认值 + key-prefix: hl-product-snapshots # 已有默认值 + max-per-product: 10 # 已有默认值 + max-keep-forever-per-product: 3 # 已有默认值 + max-size-bytes: 1048576 # 已有默认值 +``` + +--- + +## 五、向后兼容 + +- ✅ 新增功能,不影响任何现有接口 +- ✅ Flyway V20260516_004 只建新表 product_snapshot +- ✅ 老日志/老产品完全不受影响 +- ✅ 前端不实施新 UI 也不会出错(只是不显示新功能) + +--- + +## 六、不在本期范围(P2.1 #2428 + P2-4 #2426) + +- 集合深 diff(dayHotels 子项变化)— P2.1 #2428 +- 跨产品聚合可视化页 — P2-4 #2426