docs(product-v2): 产品全量快照按需保存+一键还原 (PR #2431)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
这个提交包含在:
API Changelog Bot 2026-05-16 16:51:26 +08:00
父节点 d84973ed4a
当前提交 e0250564b1

查看文件

@ -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<Long>` (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_FAILOSS 服务端错误)
### 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
<Button
type="primary"
icon={<SaveOutlined />}
disabled={!isDraft && !isCompleted} // PUBLISHED/审批中禁用
onClick={openSaveDialog}
>
保存版本
</Button>
```
保存对话框:
- 输入版本名(如"上架前最终版" / "调价前备份"
- 选填描述
- 复选框「永久保留(不被阈值清理)」
### 版本列表(侧边抽屉)
| 版本名 | 描述 | 大小 | 创建人 | 时间 | 操作 |
|---|---|---|---|---|---|
| 上架前最终版 📌 | 改价前备份 | 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: <AK>
access-key-secret: <SK>
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
- 集合深 diffdayHotels 子项变化)— P2.1 #2428
- 跨产品聚合可视化页 — P2-4 #2426