docs: 更新草原指南 OSS 前端契约
这个提交包含在:
父节点
1df4ac1a01
当前提交
6ed33870b9
@ -0,0 +1,293 @@
|
||||
# 草原指南管理、OSS 素材上传与小程序接口(一期)
|
||||
|
||||
> 日期:2026-07-12
|
||||
>
|
||||
> 后端 Issue:[HL #4919](https://git.1814.love:8443/wx/HL/issues/4919)
|
||||
>
|
||||
> 后端 PR:[HL #4921](https://git.1814.love:8443/wx/HL/pulls/4921)
|
||||
>
|
||||
> 测试分支:`dev-v3`
|
||||
>
|
||||
> 正式环境:一期发布时沿用本文契约
|
||||
|
||||
## 1. 最终方案
|
||||
|
||||
草原指南是独立的内容管理模块,视频文件仍统一进入素材库,不再使用阿里云 VOD。
|
||||
|
||||
- 后台新增一级菜单“草原指南管理”。
|
||||
- 素材库新增系统内置顶级分类“草原指南”,固定编码 `grassland_guide`。
|
||||
- 草原指南视频只接受 OSS 原始 `.mp4`,一个视频只返回一个 `videoUrl`。
|
||||
- 不转码、不生成多档清晰度、不接入 VOD 播放 SDK、不请求播放凭证。
|
||||
- 视频和封面都只能由后台管理员上传;小程序用户没有上传接口和上传权限。
|
||||
- 视频可以单独上传一张图片作为封面;未指定时由 OSS 自动截取视频首帧。
|
||||
- 小程序列表支持分页,详情直接把 `videoUrl` 交给原生 `<video>` 组件播放。
|
||||
|
||||
前端必须删除旧版草原指南 VOD 方案中的以下内容:
|
||||
|
||||
- `/admin/grassland-guide/vod/**`
|
||||
- `/mp/grassland-guide/videos/{videoId}/play-auth`
|
||||
- `vodFileId`、`playAuth`、`expiresInSeconds`
|
||||
- 阿里云 VOD Web SDK、转码状态轮询、清晰度选择器
|
||||
|
||||
## 2. 菜单、分类与权限
|
||||
|
||||
### 2.1 后台菜单
|
||||
|
||||
- 菜单名称:`草原指南管理`
|
||||
- 菜单路径:`/grassland-guide`
|
||||
- 菜单权限:`grassland-guide:list`
|
||||
|
||||
按钮权限:
|
||||
|
||||
| 功能 | 权限码 |
|
||||
|---|---|
|
||||
| 新增 | `grassland-guide:create` |
|
||||
| 编辑 | `grassland-guide:edit` |
|
||||
| 发布 | `grassland-guide:publish` |
|
||||
| 下线 | `grassland-guide:offline` |
|
||||
| 设置主推 | `grassland-guide:featured` |
|
||||
| 上传视频 | `grassland-guide:video:upload` |
|
||||
|
||||
默认授权角色为 `SUPER_ADMIN`、`OPERATOR`。
|
||||
|
||||
### 2.2 素材库分类
|
||||
|
||||
- 显示名称:`草原指南`
|
||||
- 固定编码:`grassland_guide`
|
||||
- 分类性质:系统内置顶级分类
|
||||
- 用途:视频、视频封面、正文图片
|
||||
- 约束:不能删除、停用或修改固定编码;后台错误码为 `210405`
|
||||
|
||||
前端应隐藏该顶级分类的删除、停用和修改编码入口。视频素材、封面图片都上传到这个分类;不需要创建“小程序专用”子分类。
|
||||
|
||||
## 3. 视频上传流程
|
||||
|
||||
### 3.1 前端先读取本地视频时长
|
||||
|
||||
选择 MP4 后,在请求上传凭证前读取浏览器本地元数据:
|
||||
|
||||
```ts
|
||||
export async function readVideoDurationSeconds(file: File): Promise<number> {
|
||||
const url = URL.createObjectURL(file)
|
||||
try {
|
||||
const video = document.createElement('video')
|
||||
video.preload = 'metadata'
|
||||
video.src = url
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
video.onloadedmetadata = () => resolve()
|
||||
video.onerror = () => reject(new Error('无法读取视频元数据'))
|
||||
})
|
||||
return Math.ceil(video.duration)
|
||||
} finally {
|
||||
URL.revokeObjectURL(url)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
草原指南视频的 `durationSeconds` 必填且必须大于 0。后端不会下载完整视频计算时长。
|
||||
|
||||
### 3.2 获取素材上传凭证
|
||||
|
||||
`POST /admin/material/upload/token`
|
||||
|
||||
请求示例:
|
||||
|
||||
```json
|
||||
{
|
||||
"categoryCode": "grassland_guide",
|
||||
"materialName": "呼伦贝尔草原上的风为什么特别大?",
|
||||
"filename": "hulunbuir-wind.mp4",
|
||||
"fileSize": 12925261,
|
||||
"md5": "32位文件MD5",
|
||||
"contentType": "video/mp4",
|
||||
"durationSeconds": 61
|
||||
}
|
||||
```
|
||||
|
||||
草原指南视频的强制校验:
|
||||
|
||||
- `categoryCode` 必须是 `grassland_guide`。
|
||||
- 文件扩展名必须是 `.mp4`。
|
||||
- `contentType` 必须以 `video/mp4` 开头。
|
||||
- `durationSeconds` 必须大于 0。
|
||||
|
||||
响应中的 `uploadMode` 有两种:
|
||||
|
||||
- `PRESIGNED_URL`:使用响应中的 `uploadUrl` 执行 `PUT`。
|
||||
- `STS_MULTIPART`:使用 `stsToken`、`bucket`、`region`、`ossKey` 分片上传。
|
||||
|
||||
无论哪种模式,上传时的 `Content-Type` 都必须使用接口返回的 `contentType`。STS 临时密钥只能保存在内存中,不能写入日志或本地存储。
|
||||
|
||||
若 `instantUpload=true`,直接使用响应中的 `material`,不再上传文件,也不调用确认接口。
|
||||
|
||||
### 3.3 上传完成后确认素材
|
||||
|
||||
`POST /admin/material/upload/confirm`
|
||||
|
||||
```json
|
||||
{
|
||||
"materialId": "2076195939797688322",
|
||||
"description": "草原指南视频",
|
||||
"tagIds": []
|
||||
}
|
||||
```
|
||||
|
||||
确认成功后保存返回的 `materialId`,创建草原指南内容时作为 `videoMaterialId` 提交。
|
||||
|
||||
### 3.4 文件夹上传
|
||||
|
||||
如素材库继续支持文件夹批量上传,`POST /admin/material/upload/folder` 的每个 `files[]` 项也必须为草原指南视频提交 `durationSeconds`。
|
||||
|
||||
## 4. 视频封面
|
||||
|
||||
封面图片同样通过素材库通用上传接口上传到 `grassland_guide`,图片不传 `durationSeconds`。
|
||||
|
||||
- 指定 `customCoverMaterialId`:详情返回 `coverSource=CUSTOM`,使用管理员上传的图片。
|
||||
- 不指定或清空 `customCoverMaterialId`:详情返回 `coverSource=AUTO`,后端使用视频素材的 OSS 自动截帧地址。
|
||||
|
||||
OSS 自动封面格式:
|
||||
|
||||
```text
|
||||
{videoUrl}?x-oss-process=video/snapshot,t_0,f_jpg,w_800,m_fast
|
||||
```
|
||||
|
||||
前端只使用响应中的 `effectiveCoverUrl`/`coverUrl`,不要自行拼接处理参数。
|
||||
|
||||
## 5. 草原指南管理接口
|
||||
|
||||
基础路径:`/admin/grassland-guide/videos`
|
||||
|
||||
| 方法 | 路径 | 说明 |
|
||||
|---|---|---|
|
||||
| `GET` | `/admin/grassland-guide/videos` | 分页列表 |
|
||||
| `GET` | `/admin/grassland-guide/videos/{videoId}` | 详情 |
|
||||
| `POST` | `/admin/grassland-guide/videos` | 创建草稿 |
|
||||
| `PUT` | `/admin/grassland-guide/videos/{videoId}` | 全量更新,必须传当前 `version` |
|
||||
| `PUT` | `/admin/grassland-guide/videos/{videoId}/publish` | 发布/重新发布 |
|
||||
| `PUT` | `/admin/grassland-guide/videos/{videoId}/offline` | 下线 |
|
||||
|
||||
创建或全量更新的主体:
|
||||
|
||||
```json
|
||||
{
|
||||
"title": "呼伦贝尔草原上的风为什么特别大?",
|
||||
"summary": "视频摘要",
|
||||
"videoMaterialId": "2076195939797688322",
|
||||
"customCoverMaterialId": "2076196060279070722",
|
||||
"contentHtml": "<p>正文内容</p>",
|
||||
"contentImageMaterialIds": [],
|
||||
"featured": true,
|
||||
"showProducedBadge": true,
|
||||
"sortWeight": 300,
|
||||
"publishTime": null,
|
||||
"linkedProductId": null,
|
||||
"relatedVideoIds": ["2076196084920610817", "2076196086652858370"],
|
||||
"version": 2
|
||||
}
|
||||
```
|
||||
|
||||
`version` 只在更新时必填。创建草稿允许内容暂时不完整,发布时必须满足:
|
||||
|
||||
- 标题、视频素材和有效封面完整。
|
||||
- 视频素材为正常状态的 `grassland_guide` OSS MP4。
|
||||
- 视频存在 `ossUrl`、`thumbnailUrl` 和正数时长。
|
||||
- 正文包含有效文字或正文图片。
|
||||
- 配置 2~3 条不重复、非自身的相关推荐。
|
||||
|
||||
后台详情返回新增字段:
|
||||
|
||||
```json
|
||||
{
|
||||
"videoMaterialId": "2076195939797688322",
|
||||
"videoUrl": "https://.../the-daily-dweebs-1080.mp4",
|
||||
"customCoverMaterialId": "2076196060279070722",
|
||||
"coverSource": "CUSTOM",
|
||||
"effectiveCoverUrl": "https://.../cover.jpg",
|
||||
"durationSeconds": 61
|
||||
}
|
||||
```
|
||||
|
||||
所有雪花 ID 在 JavaScript 中都按字符串处理,避免超过安全整数范围。
|
||||
|
||||
## 6. 小程序接口
|
||||
|
||||
小程序只保留三个只读接口:
|
||||
|
||||
| 方法 | 路径 | 说明 |
|
||||
|---|---|---|
|
||||
| `GET` | `/mp/grassland-guide/home?page=1&pageSize=20` | 精选区与视频分页 |
|
||||
| `GET` | `/mp/grassland-guide/videos?page=1&pageSize=20` | 视频分页列表 |
|
||||
| `GET` | `/mp/grassland-guide/videos/{videoId}` | 视频详情 |
|
||||
|
||||
分页规则:
|
||||
|
||||
- `page` 默认 `1`,小于 `1` 时按 `1` 处理。
|
||||
- `pageSize` 默认 `20`,最大 `100`。
|
||||
- 只返回已发布内容,按发布时间倒序。
|
||||
- 标准分页字段为 `records`、`total`、`page`、`pageSize`;当前响应不返回 `totalPages`,需要时由前端计算。
|
||||
|
||||
详情直接返回 OSS 原视频地址:
|
||||
|
||||
```json
|
||||
{
|
||||
"videoId": "2076196082752155649",
|
||||
"title": "草原指南测试|The Daily Dweebs",
|
||||
"videoUrl": "https://.../the-daily-dweebs-1080.mp4",
|
||||
"durationSeconds": 61,
|
||||
"coverSource": "CUSTOM",
|
||||
"coverUrl": "https://.../cover.jpg",
|
||||
"contentHtml": "<p>正文内容</p>",
|
||||
"relatedVideos": []
|
||||
}
|
||||
```
|
||||
|
||||
小程序直接使用原生组件:
|
||||
|
||||
```html
|
||||
<video src="{{detail.videoUrl}}" controls></video>
|
||||
```
|
||||
|
||||
OSS MP4 支持 Range 请求,原生 `<video>` 能从 MP4 元数据读取并显示当前播放时间、总时长和进度条。卡片时长使用接口的 `durationSeconds` 格式化即可,不做清晰度切换。
|
||||
|
||||
## 7. 测试环境验收结果
|
||||
|
||||
`dev-v3` 已于 2026-07-12 完成部署和真实数据验证:
|
||||
|
||||
- `hl-resource-service`:`8082/8182`,Nacos `2/2` 健康。
|
||||
- `hl-mp-service`:`8085/8185`,Nacos `2/2` 健康。
|
||||
- `hl-gateway`:`8080/8180`,Nacos `2/2` 健康。
|
||||
- `hl-user-service`:`8081/8181`,Nacos `2/2` 健康。
|
||||
- User Flyway `20260712.005` 执行成功;Resource Flyway `20260712.001` 执行成功。
|
||||
- Knife4j 已展示草原指南管理接口、素材上传接口及 `durationSeconds`;小程序文档只剩上述 3 个接口。
|
||||
|
||||
真实数据:
|
||||
|
||||
| 内容 | 素材 ID | 内容 ID | 时长 | 封面 |
|
||||
|---|---|---|---:|---|
|
||||
| The Daily Dweebs | `2076195939797688322` | `2076196082752155649` | 61 秒 | `CUSTOM` |
|
||||
| Caminandes 3: Llamigos | `2076195998660550657` | `2076196084920610817` | 151 秒 | `AUTO` |
|
||||
| Glass Half | `2076196033146118145` | `2076196086652858370` | 194 秒 | `AUTO` |
|
||||
|
||||
验收结果:
|
||||
|
||||
- 三个文件都是不同的 Blender 官方真实作品,不是同一视频的多档清晰度。
|
||||
- 三个视频均通过 `STS_MULTIPART` 上传,分类为 `grassland_guide`,存储方为 `OSS`。
|
||||
- 自定义封面通过 `PRESIGNED_URL` 上传。
|
||||
- MP4 Range 请求均返回 `206 video/mp4`。
|
||||
- 自定义封面和 OSS 自动封面均返回 `200 image/*`。
|
||||
- 小程序 `pageSize=2` 实测分页为第 1 页 2 条、第 2 页 1 条,无重复。
|
||||
- 三个详情均直接返回 `videoUrl`、正确时长和 2 条相关推荐。
|
||||
- 旧 `/play-auth` 接口已不可用。
|
||||
|
||||
## 8. 前端改造检查单
|
||||
|
||||
- [ ] 新增“草原指南管理”列表、编辑和发布页面。
|
||||
- [ ] 素材库展示顶级分类“草原指南”,隐藏系统分类删除/停用/改码操作。
|
||||
- [ ] 选择 MP4 后通过 `loadedmetadata` 获取时长并 `Math.ceil`。
|
||||
- [ ] 使用素材库通用上传接口处理 `PRESIGNED_URL` 和 `STS_MULTIPART`。
|
||||
- [ ] 支持单独上传/选择图片作为自定义封面,并允许清空后恢复自动封面。
|
||||
- [ ] 草原指南内容保存 `videoMaterialId`,不保存 VOD 字段。
|
||||
- [ ] 小程序列表接入分页参数和分页结果。
|
||||
- [ ] 小程序详情直接使用 `videoUrl` 播放。
|
||||
- [ ] 删除 VOD SDK、`play-auth` 调用和清晰度切换 UI。
|
||||
- [ ] 所有雪花 ID 均按字符串处理。
|
||||
@ -1,524 +0,0 @@
|
||||
# 草原指南管理、VOD 素材与小程序接口
|
||||
|
||||
> **服务**: `hl-user-service`、`hl-resource-service`、`hl-mp-service`、`hl-gateway`
|
||||
>
|
||||
> **PR**: [#4916](https://git.1814.love:8443/wx/HL/pulls/4916)、[#4917](https://git.1814.love:8443/wx/HL/pulls/4917)、[#4918](https://git.1814.love:8443/wx/HL/pulls/4918)
|
||||
>
|
||||
> **Issue**: [#4914](https://git.1814.love:8443/wx/HL/issues/4914)
|
||||
>
|
||||
> **日期**: 2026-07-12
|
||||
>
|
||||
> **影响范围**: 管理后台草原指南内容维护、素材库草原指南分类、小程序草原指南列表/详情/播放
|
||||
|
||||
---
|
||||
|
||||
## 一、前端必须先确认的结论
|
||||
|
||||
1. **草原指南是独立内容模块**,不是“小程序专用素材库列表”;页面不做内容分类筛选。
|
||||
2. 素材库新增固定顶级分类:
|
||||
- 名称:`草原指南`
|
||||
- 编码:`grassland_guide`
|
||||
- 编码固定,不能删除、停用或修改;显示名称和排序可以调整。
|
||||
3. 视频、可选自定义封面、富文本图片都由后台管理员上传;**小程序用户没有上传接口**。
|
||||
4. 视频文件走阿里云 VOD,确认后自动注册为 `grassland_guide` 分类的视频素材。
|
||||
5. 视频封面规则:
|
||||
- 传 `customCoverMaterialId`:使用管理员上传的图片,`coverSource=CUSTOM`;
|
||||
- 不传或传 `null`:使用阿里云 VOD 自动截帧封面,`coverSource=AUTO`。
|
||||
6. 本次没有新增 `/mp/material/miniprogram/page`,小程序草原指南只调用本文的 `/mp/grassland-guide/**`。
|
||||
7. 所有业务失败仍可能返回 HTTP 200;前端必须判断响应体 `code` / `success`。
|
||||
|
||||
---
|
||||
|
||||
## 二、管理后台菜单与权限
|
||||
|
||||
| 配置 | 值 |
|
||||
|---|---|
|
||||
| 菜单名 | `草原指南管理` |
|
||||
| 路由 | `/grassland-guide` |
|
||||
| 组件路径 | `grassland-guide/GrasslandGuideList` |
|
||||
| 图标 | `VideocamOutline` |
|
||||
| 列表权限 | `grassland-guide:list` |
|
||||
| 新增权限 | `grassland-guide:create` |
|
||||
| 编辑权限 | `grassland-guide:edit` |
|
||||
| 发布权限 | `grassland-guide:publish` |
|
||||
| 下线权限 | `grassland-guide:offline` |
|
||||
| 设置精选权限 | `grassland-guide:featured` |
|
||||
| 上传视频权限 | `grassland-guide:vod:upload` |
|
||||
|
||||
菜单及按钮已分配给 `SUPER_ADMIN`、`OPERATOR`。
|
||||
|
||||
- 所有管理端接口均要求管理员登录。
|
||||
- 列表、详情没有额外业务角色限制。
|
||||
- 创建、更新、发布、下线、VOD 上传仅允许 `SUPER_ADMIN` 或 `OPERATOR`。
|
||||
- 当前没有“删除草原指南内容”接口,只允许下线。
|
||||
|
||||
---
|
||||
|
||||
## 三、接口总表
|
||||
|
||||
### 管理端内容接口
|
||||
|
||||
| 方法 | 路径 | 说明 |
|
||||
|---|---|---|
|
||||
| `GET` | `/admin/grassland-guide/videos` | 分页查询 |
|
||||
| `GET` | `/admin/grassland-guide/videos/{videoId}` | 内容详情 |
|
||||
| `POST` | `/admin/grassland-guide/videos` | 创建草稿 |
|
||||
| `PUT` | `/admin/grassland-guide/videos/{videoId}` | 全量更新 |
|
||||
| `PUT` | `/admin/grassland-guide/videos/{videoId}/publish` | 发布/重新发布 |
|
||||
| `PUT` | `/admin/grassland-guide/videos/{videoId}/offline` | 下线 |
|
||||
|
||||
### 管理端 VOD 接口
|
||||
|
||||
| 方法 | 路径 | 说明 |
|
||||
|---|---|---|
|
||||
| `POST` | `/admin/grassland-guide/vod/upload/create` | 创建 VOD 上传会话 |
|
||||
| `POST` | `/admin/grassland-guide/vod/upload/refresh` | 刷新上传凭证 |
|
||||
| `POST` | `/admin/grassland-guide/vod/upload/confirm` | 确认上传并注册素材 |
|
||||
| `GET` | `/admin/grassland-guide/vod/materials/{materialId}/play-auth` | 后台预览播放凭证 |
|
||||
|
||||
### 小程序只读接口
|
||||
|
||||
| 方法 | 路径 | 说明 |
|
||||
|---|---|---|
|
||||
| `GET` | `/mp/grassland-guide/home` | 精选区 + 视频分页 |
|
||||
| `GET` | `/mp/grassland-guide/videos` | 视频分页 |
|
||||
| `GET` | `/mp/grassland-guide/videos/{videoId}` | 视频详情 |
|
||||
| `GET` | `/mp/grassland-guide/videos/{videoId}/play-auth` | 短效播放凭证 |
|
||||
|
||||
小程序仅放行以上精确 `GET` 路径;同路径的 `POST`、`PUT`、`DELETE` 不放行。
|
||||
|
||||
---
|
||||
|
||||
## 四、管理端视频内容契约
|
||||
|
||||
### 4.1 分页查询 `GET /admin/grassland-guide/videos`
|
||||
|
||||
#### Query
|
||||
|
||||
| 字段 | 类型 | 必填 | 默认/约束 | 说明 |
|
||||
|---|---|---:|---|---|
|
||||
| `keyword` | String | 否 | - | 标题、摘要模糊匹配 |
|
||||
| `status` | String | 否 | `DRAFT/PUBLISHED/OFFLINE` | 状态精确过滤 |
|
||||
| `featured` | Boolean | 否 | - | 是否精选 |
|
||||
| `page` | Integer | 否 | 默认 1,最小 1 | 页码 |
|
||||
| `pageSize` | Integer | 否 | 默认 20,范围 1–100 | 每页条数 |
|
||||
|
||||
固定排序:`publishTime DESC, videoId DESC`。管理列表不按 `sortWeight` 排序。
|
||||
|
||||
#### `PageResult<GrasslandGuideVideoListVO>`
|
||||
|
||||
分页对象只有 `records`、`total`、`page`、`pageSize`,没有 `totalPages`。
|
||||
|
||||
| 列表项字段 | 类型 | 说明 |
|
||||
|---|---|---|
|
||||
| `videoId` | String | 内容 ID |
|
||||
| `title` | String | 标题 |
|
||||
| `summary` | String | 摘要 |
|
||||
| `effectiveCoverUrl` | String | 实际生效的封面 |
|
||||
| `coverSource` | String | `AUTO/CUSTOM` |
|
||||
| `durationSeconds` | Long | 视频时长(秒) |
|
||||
| `featured` | Boolean | 是否精选 |
|
||||
| `showProducedBadge` | Boolean | 是否显示“呼籁出品”角标 |
|
||||
| `sortWeight` | Integer | 精选排序权重 |
|
||||
| `status` | String | `DRAFT/PUBLISHED/OFFLINE` |
|
||||
| `publishTime` | LocalDateTime | 首次发布时间 |
|
||||
| `linkedProductId` | String | 预留关联产品 ID,一期不做跳转 |
|
||||
|
||||
所有雪花 ID 在响应中按 String 返回,前端不要转成 JavaScript `Number`。
|
||||
|
||||
### 4.2 详情 `GET /admin/grassland-guide/videos/{videoId}`
|
||||
|
||||
详情包含列表项全部字段,并增加:
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|---|---|---|
|
||||
| `videoMaterialId` | String | VOD 视频素材 ID |
|
||||
| `customCoverMaterialId` | String | 自定义封面素材 ID,可空 |
|
||||
| `coverMaterialId` | String | 一期兼容字段,值同 `customCoverMaterialId` |
|
||||
| `contentHtml` | String | 白名单清洗后的正文 HTML |
|
||||
| `contentImageMaterialIds` | `String[]` | 正文图片素材 ID |
|
||||
| `version` | Integer | 乐观锁版本 |
|
||||
| `createdBy` | String | 创建管理员 ID |
|
||||
| `updatedBy` | String | 最后更新管理员 ID |
|
||||
| `createdAt` | LocalDateTime | 创建时间 |
|
||||
| `updatedAt` | LocalDateTime | 更新时间 |
|
||||
| `relatedVideos` | `GrasslandGuideVideoListVO[]` | 按手工顺序返回的相关推荐 |
|
||||
|
||||
### 4.3 创建草稿 `POST /admin/grassland-guide/videos`
|
||||
|
||||
创建允许字段暂不完整,返回的初始状态固定为 `DRAFT`、`version=1`。
|
||||
|
||||
### 4.4 全量更新 `PUT /admin/grassland-guide/videos/{videoId}`
|
||||
|
||||
创建和更新共用以下字段;更新时额外必传 `version`。
|
||||
|
||||
| 字段 | 类型 | DTO 必填 | 约束/语义 |
|
||||
|---|---|---:|---|
|
||||
| `title` | String | 否 | 最长 200;空白按 null |
|
||||
| `summary` | String | 否 | 最长 1000;空白按 null |
|
||||
| `videoMaterialId` | Long/String | 否 | `grassland_guide` 分类的可播放 VOD 素材 |
|
||||
| `customCoverMaterialId` | Long/String | 否 | 自定义图片封面;兼容输入别名 `coverMaterialId` |
|
||||
| `contentHtml` | String | 否 | 富文本正文 |
|
||||
| `contentImageMaterialIds` | Long[]/String[] | 否 | 正文图片素材 ID,元素不能为 null |
|
||||
| `featured` | Boolean | 否 | 未传按 false |
|
||||
| `showProducedBadge` | Boolean | 否 | 未传按 false |
|
||||
| `sortWeight` | Integer | 否 | 未传按 0 |
|
||||
| `publishTime` | LocalDateTime | 否 | 首次发布未传时由服务端补当前时间 |
|
||||
| `linkedProductId` | Long/String | 否 | 二期预留,一期不做产品跳转 |
|
||||
| `relatedVideoIds` | Long[]/String[] | 否 | 最多 3 条,保持数组顺序 |
|
||||
| `version` | Integer | 更新必填 | 必须等于详情返回的当前版本 |
|
||||
|
||||
> **更新是全量覆盖,不是 PATCH。** 未传布尔值会写为 false,未传 `sortWeight` 会写为 0,未传正文图片/相关推荐会清空,未传自定义封面会切回 `AUTO`。
|
||||
|
||||
示例:
|
||||
|
||||
```json
|
||||
{
|
||||
"title": "呼伦贝尔草原上的风为什么特别大?",
|
||||
"summary": "从地形和季节理解草原的风。",
|
||||
"videoMaterialId": "2030000000000001",
|
||||
"customCoverMaterialId": null,
|
||||
"contentHtml": "<p>正文内容</p>",
|
||||
"contentImageMaterialIds": [],
|
||||
"featured": true,
|
||||
"showProducedBadge": true,
|
||||
"sortWeight": 100,
|
||||
"publishTime": null,
|
||||
"linkedProductId": null,
|
||||
"relatedVideoIds": ["2030000000000002", "2030000000000003"],
|
||||
"version": 1
|
||||
}
|
||||
```
|
||||
|
||||
### 4.5 发布/下线
|
||||
|
||||
请求体一致:
|
||||
|
||||
```json
|
||||
{ "version": 2 }
|
||||
```
|
||||
|
||||
允许的状态流转只有:
|
||||
|
||||
```text
|
||||
DRAFT -> PUBLISHED -> OFFLINE -> PUBLISHED
|
||||
```
|
||||
|
||||
- 不允许退回 `DRAFT`。
|
||||
- 全量更新、发布、下线成功后都会 `version + 1`。
|
||||
- 首次从草稿发布且 `publishTime` 为空时,服务端自动填当前时间。
|
||||
- 下线后重新发布保留首次发布时间。
|
||||
|
||||
### 4.6 发布门禁
|
||||
|
||||
发布前必须同时满足:
|
||||
|
||||
- 标题非空;
|
||||
- 有效 VOD 视频素材,媒体状态为 `Normal`,时长大于 0;
|
||||
- 有生效封面;
|
||||
- 正文至少有有效文字或一张图片;
|
||||
- 有发布时间;
|
||||
- 有 2–3 条相关推荐。
|
||||
|
||||
相关推荐不能为 null、不能重复、不能指向自身,且目标内容必须存在。管理端按请求数组顺序保存;小程序详情只返回其中仍为 `PUBLISHED` 的内容,不自动补位。
|
||||
|
||||
富文本会做白名单清洗;`img.src` 只允许 HTTP/HTTPS,并且必须与 `contentImageMaterialIds` 对应素材 URL 集合完全一致。
|
||||
|
||||
---
|
||||
|
||||
## 五、VOD 上传与素材注册
|
||||
|
||||
### 5.1 创建上传会话
|
||||
|
||||
`POST /admin/grassland-guide/vod/upload/create`
|
||||
|
||||
```json
|
||||
{
|
||||
"title": "草原指南测试视频",
|
||||
"fileName": "grassland-guide.mp4"
|
||||
}
|
||||
```
|
||||
|
||||
| 入参字段 | 类型 | 必填 | 约束 |
|
||||
|---|---|---:|---|
|
||||
| `title` | String | 是 | 1–128 字符 |
|
||||
| `fileName` | String | 是 | 1–255 字符,保留真实扩展名;支持 mp4/mov/avi/mkv/wmv/flv/webm |
|
||||
|
||||
返回 `data`:
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|---|---|---|
|
||||
| `uploadSessionId` | String | 后续 refresh/confirm 使用 |
|
||||
| `vodFileId` | String | 阿里云 VideoId |
|
||||
| `uploadAddress` | String | 阿里云上传地址,按不透明字符串处理 |
|
||||
| `uploadAuth` | String | 阿里云上传凭证,按不透明敏感字符串处理 |
|
||||
|
||||
`uploadAddress`、`uploadAuth` 禁止写日志、LocalStorage 或业务数据库。
|
||||
|
||||
### 5.2 刷新上传凭证
|
||||
|
||||
`POST /admin/grassland-guide/vod/upload/refresh`
|
||||
|
||||
```json
|
||||
{ "uploadSessionId": "会话ID" }
|
||||
```
|
||||
|
||||
返回结构同创建接口。阿里云浏览器上传 SDK 触发凭证过期回调时调用此接口,并把新凭证交回 SDK。
|
||||
|
||||
### 5.3 确认上传并注册素材
|
||||
|
||||
`POST /admin/grassland-guide/vod/upload/confirm`
|
||||
|
||||
```json
|
||||
{
|
||||
"uploadSessionId": "会话ID",
|
||||
"materialName": "草原指南测试视频"
|
||||
}
|
||||
```
|
||||
|
||||
| 字段 | 类型 | 必填 | 约束 |
|
||||
|---|---|---:|---|
|
||||
| `uploadSessionId` | String | 是 | 非空 |
|
||||
| `materialName` | String | 否 | 最长 200 |
|
||||
|
||||
只有阿里云媒体状态为 `Normal` 时才会确认成功。若视频仍在转码/截图处理中,前端保留会话 ID,稍后重试 confirm,不要重新创建上传会话。重复 confirm 是幂等的,会返回同一个 `fileId/materialId`,并刷新异步生成的时长和封面。
|
||||
|
||||
成功响应:
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "成功",
|
||||
"data": {
|
||||
"file": {
|
||||
"fileId": "...",
|
||||
"storageProvider": "VOD",
|
||||
"providerFileId": "阿里云VideoId",
|
||||
"durationSeconds": 125,
|
||||
"mediaStatus": "Normal",
|
||||
"thumbnailUrl": "阿里云自动封面URL"
|
||||
},
|
||||
"material": {
|
||||
"materialId": "...",
|
||||
"categoryCode": "grassland_guide",
|
||||
"fileType": "VIDEO",
|
||||
"storageProvider": "VOD",
|
||||
"providerFileId": "阿里云VideoId",
|
||||
"durationSeconds": 125,
|
||||
"mediaStatus": "Normal",
|
||||
"thumbnailUrl": "阿里云自动封面URL"
|
||||
}
|
||||
},
|
||||
"success": true
|
||||
}
|
||||
```
|
||||
|
||||
前端保存 `data.material.materialId`,作为内容表单的 `videoMaterialId`。
|
||||
|
||||
### 5.4 推荐的浏览器上传顺序
|
||||
|
||||
```text
|
||||
选择本地视频
|
||||
-> create 获取 uploadAddress/uploadAuth/uploadSessionId
|
||||
-> 使用阿里云 VOD 浏览器上传 SDK 直传
|
||||
-> 凭证过期时调用 refresh
|
||||
-> SDK 报告上传完成后调用 confirm
|
||||
-> confirm 返回 materialId
|
||||
-> 创建/更新草原指南草稿
|
||||
```
|
||||
|
||||
不要把视频二进制提交给 HL 后端;后端只签发上传凭证、确认媒体状态并登记素材。
|
||||
|
||||
### 5.5 后台预览播放凭证
|
||||
|
||||
`GET /admin/grassland-guide/vod/materials/{materialId}/play-auth`
|
||||
|
||||
返回:
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|---|---|---|
|
||||
| `vodFileId` | String | 阿里云 VideoId |
|
||||
| `playAuth` | String | 短效播放凭证 |
|
||||
| `expiresInSeconds` | Long | 本次凭证有效期,以返回值为准 |
|
||||
| `durationSeconds` | Long | 时长(秒) |
|
||||
| `coverUrl` | String | 播放器封面 |
|
||||
|
||||
每次打开播放器或凭证即将过期时重新请求;不要缓存、持久化 `playAuth`。
|
||||
|
||||
---
|
||||
|
||||
## 六、自定义封面与正文图片上传
|
||||
|
||||
图片继续复用现有素材库 OSS 上传流程:
|
||||
|
||||
1. `POST /admin/material/upload/token`
|
||||
2. 浏览器按返回凭证直传 OSS
|
||||
3. `POST /admin/material/upload/confirm`
|
||||
|
||||
请求上传凭证时固定传:
|
||||
|
||||
```json
|
||||
{
|
||||
"categoryCode": "grassland_guide",
|
||||
"materialName": "视频自定义封面",
|
||||
"filename": "cover.jpg",
|
||||
"fileSize": 123456,
|
||||
"md5": "32位十六进制MD5",
|
||||
"contentType": "image/jpeg"
|
||||
}
|
||||
```
|
||||
|
||||
- 自定义封面、正文图片必须是 `grassland_guide` 分类的有效图片素材。
|
||||
- 有自定义封面时传 `customCoverMaterialId`;不传/null 时后端使用 VOD 自动封面。
|
||||
- 自动封面读取顺序为阿里云 `coverURL`,为空时取 snapshots 中第一张有效图片;后端当前不主动发起截图任务。
|
||||
- 如果阿里云自动封面仍未生成,草稿可以保存,但发布会因缺少生效封面返回 `371004`;稍后重试发布会先刷新 VOD 元数据。
|
||||
- 删除、批量删除或移出分类时,如果素材正在被草原指南引用,返回 `371009`。
|
||||
- 固定顶级分类本身不能删除、停用或修改编码,保护错误码为 `210405`。
|
||||
|
||||
---
|
||||
|
||||
## 七、小程序接口详情
|
||||
|
||||
### 7.1 首页 `GET /mp/grassland-guide/home`
|
||||
|
||||
Query:`page` 默认 1;`pageSize` 默认 20、最大 100。
|
||||
|
||||
返回:
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "成功",
|
||||
"data": {
|
||||
"featuredVideos": [],
|
||||
"videos": {
|
||||
"records": [],
|
||||
"total": 0,
|
||||
"page": 1,
|
||||
"pageSize": 20
|
||||
}
|
||||
},
|
||||
"success": true
|
||||
}
|
||||
```
|
||||
|
||||
- `featuredVideos` 最多 5 条,只取已发布且 `featured=true` 的内容。
|
||||
- 精选排序:`sortWeight DESC -> publishTime DESC -> videoId DESC`。
|
||||
- 没有精选时返回空数组,不使用普通视频兜底。
|
||||
- `videos` 返回全部已发布内容,排序为 `publishTime DESC -> videoId DESC`。
|
||||
|
||||
### 7.2 视频分页 `GET /mp/grassland-guide/videos`
|
||||
|
||||
Query 同首页:`page`、`pageSize`。
|
||||
|
||||
卡片字段:
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|---|---|---|
|
||||
| `videoId` | String | 内容 ID |
|
||||
| `title` | String | 标题 |
|
||||
| `summary` | String | 摘要 |
|
||||
| `coverUrl` | String | 生效封面 |
|
||||
| `coverSource` | String | `AUTO/CUSTOM` |
|
||||
| `durationSeconds` | Long | 时长(秒) |
|
||||
|
||||
精选项在卡片字段基础上增加 `showProducedBadge`。
|
||||
|
||||
### 7.3 视频详情 `GET /mp/grassland-guide/videos/{videoId}`
|
||||
|
||||
在卡片字段基础上增加:
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|---|---|---|
|
||||
| `publishTime` | LocalDateTime | 发布时间 |
|
||||
| `contentHtml` | String | 已清洗正文 |
|
||||
| `relatedVideos` | 视频卡片数组 | 只包含仍在线的推荐内容,保持手工顺序 |
|
||||
| `showProducedBadge` | Boolean | 是否显示“呼籁出品”角标 |
|
||||
|
||||
### 7.4 播放凭证 `GET /mp/grassland-guide/videos/{videoId}/play-auth`
|
||||
|
||||
返回字段与后台预览一致:`vodFileId`、`playAuth`、`expiresInSeconds`、`durationSeconds`、`coverUrl`。
|
||||
|
||||
- 仅已发布内容可获取。
|
||||
- 接口不缓存,避免返回过期凭证。
|
||||
- 前端不得把 `playAuth` 当成长期播放 URL。
|
||||
|
||||
---
|
||||
|
||||
## 八、主要错误码
|
||||
|
||||
| code | 说明 | 前端动作 |
|
||||
|---:|---|---|
|
||||
| `400` | DTO 校验、枚举或 JSON 格式错误 | 展示具体 message |
|
||||
| `401` | 未登录/小程序写请求被拦截 | 走登录或停止请求 |
|
||||
| `403` | 非 `SUPER_ADMIN/OPERATOR` 执行写操作 | 隐藏操作并提示无权限 |
|
||||
| `371001` | 草原指南视频不存在或未发布 | 返回列表/显示已下线 |
|
||||
| `371002` | version 冲突 | 重新拉详情后再编辑 |
|
||||
| `371003` | 非法状态流转 | 刷新状态 |
|
||||
| `371004` | 发布条件不完整 | 按 message 补齐字段 |
|
||||
| `371005` | 视频/封面/正文图片素材无效 | 重新选择素材 |
|
||||
| `371006` | 相关推荐无效 | 检查数量、重复、自身和目标 ID |
|
||||
| `371007` | 正文图片 URL 与素材声明不一致 | 同步 `contentHtml` 和图片 ID |
|
||||
| `371009` | 素材正被草原指南引用 | 先解除内容引用 |
|
||||
| `210405` | 固定素材分类受保护 | 不提供删除/停用/改编码入口 |
|
||||
| `250507` | VOD 媒体尚未处理完成 | 稍后重试 confirm |
|
||||
| `250511` | VOD 确认处理中 | 防重复点击,稍后重试 |
|
||||
| `503` | user/resource 下游暂不可用 | 允许重试,不要当业务空数据 |
|
||||
|
||||
---
|
||||
|
||||
## 九、前端实施检查单
|
||||
|
||||
- [ ] 新建 `grassland-guide/GrasslandGuideList` 并接入动态菜单。
|
||||
- [ ] 内容列表不增加“内容分类”筛选,只保留关键词、状态、精选筛选。
|
||||
- [ ] ID 全程按 String 保存和回传,避免精度丢失。
|
||||
- [ ] 编辑前先拉详情,更新/发布/下线始终提交最新 `version`。
|
||||
- [ ] 更新接口按全量表单提交,不使用局部 PATCH 心智。
|
||||
- [ ] VOD 采用 create -> SDK 直传 -> refresh(按需)-> confirm 流程。
|
||||
- [ ] 上传凭证只保存在当前上传任务内,不落 LocalStorage、不打印。
|
||||
- [ ] 图片上传固定使用 `categoryCode=grassland_guide`。
|
||||
- [ ] 自定义封面可清空;清空后展示后端返回的 VOD 自动封面。
|
||||
- [ ] 小程序播放器按需调用 play-auth,并按 `expiresInSeconds` 管理凭证。
|
||||
- [ ] 小程序实现列表骨架屏、空态、加载失败重试;接口空态为 200 + 空数组/空分页。
|
||||
- [ ] 不给小程序增加任何视频、封面或正文图片上传入口。
|
||||
|
||||
---
|
||||
|
||||
## 十、测试环境已验证
|
||||
|
||||
测试分支:`dev-v3`,合并提交:`4d4f090dcc47527e247177277313eec636ec0ada`。
|
||||
|
||||
```text
|
||||
hl-user-service 8081 / 8181 Nacos healthy=true ✓
|
||||
hl-resource-service 8082 / 8182 Nacos healthy=true ✓
|
||||
hl-mp-service 8085 / 8185 Nacos healthy=true ✓
|
||||
hl-gateway 8080 / 8180 Nacos healthy=true ✓
|
||||
|
||||
GET /mp/grassland-guide/home -> 200,精选空数组 + 空分页 ✓
|
||||
GET /mp/grassland-guide/videos -> 200,PageResult total=0 ✓
|
||||
GET /mp/grassland-guide/videos/{不存在ID} -> code=371001 ✓
|
||||
POST /mp/grassland-guide/home -> code=401 ✓
|
||||
GET /admin/grassland-guide/videos(未登录) -> code=401 ✓
|
||||
GET /admin/grassland-guide/videos(管理员) -> 200,total=0 ✓
|
||||
GET /admin/menu/my -> 草原指南菜单及组件路径存在 ✓
|
||||
GET /admin/dict/data/material_category -> grassland_guide=ACTIVE ✓
|
||||
POST /admin/grassland-guide/vod/upload/create(空请求) -> code=400,路由与校验生效 ✓
|
||||
阿里云 VOD GetCategories -> HTTP 200,当前 AK 具备 VOD 访问权限 ✓
|
||||
```
|
||||
|
||||
Flyway:
|
||||
|
||||
- User `V20260712_002`、`V20260712_004` 成功;
|
||||
- Resource `V20260712_001` 成功;
|
||||
- 菜单 7 条、固定分类 1 条、VOD 会话表、3 张草原指南内容表均已落库。
|
||||
|
||||
> 本轮没有创建真实云端测试视频,以免产生孤儿 VideoId/上传会话;真实大文件上传仍需前端联调时用测试视频执行一次 create -> 直传 -> confirm。
|
||||
|
||||
---
|
||||
|
||||
## 十一、不影响范围
|
||||
|
||||
- 不修改现有通用素材列表接口。
|
||||
- 不增加小程序素材库上传或分页接口。
|
||||
- 不修改 `hl-ui` 现有代码;本文为前端实施契约。
|
||||
- `linkedProductId` 仅持久化,产品跳转留到二期。
|
||||
正在加载...
x
在新工单中引用
屏蔽一个用户