15 KiB
草原指南管理、OSS 素材上传与小程序接口(一期)
日期:2026-07-12
后端 Issue:HL #4919、HL #4922、HL #4927
后端 PR:HL #4921、HL #4924、HL #4929
测试分支:
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-authvodFileId、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 后,在请求上传凭证前读取浏览器本地元数据:
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
请求示例:
{
"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。- 单个视频最大
2147483648字节(2 GiB),大于10485760字节(10 MiB)时使用 OSS 分片上传。
响应中的 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
{
"materialId": "2076195939797688322",
"description": "草原指南视频",
"tagIds": []
}
确认接口会通过 OSS HeadObject 读取实际对象元数据,并校验:
- OSS 对象必须真实存在。
- 实际
Content-Length必须等于申请上传凭证时声明的fileSize。 - MP4 的实际
Content-Type必须是video/mp4(允许携带参数)。
任一校验失败时素材不会进入 ACTIVE。确认成功后保存返回的 materialId,创建草原指南内容时作为 videoMaterialId 提交。
3.4 文件夹上传
如素材库继续支持文件夹批量上传,POST /admin/material/upload/folder 的每个 files[] 项也必须为草原指南视频提交 durationSeconds。
4. 视频封面
封面图片同样通过素材库通用上传接口上传到 grassland_guide,图片不传 durationSeconds。
- 指定
customCoverMaterialId:详情返回coverSource=CUSTOM,使用管理员上传的图片。 - 不指定或清空
customCoverMaterialId:详情返回coverSource=AUTO,后端使用视频素材的 OSS 自动截帧地址。
OSS 自动封面格式:
{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 |
下线 |
创建或全量更新的主体:
{
"title": "呼伦贝尔草原上的风为什么特别大?",
"summary": "视频摘要",
"videoMaterialId": "2076195939797688322",
"customCoverMaterialId": "2076196060279070722",
"contentHtml": "<p>正文内容</p>",
"contentImageMaterialIds": [],
"featured": true,
"showProducedBadge": true,
"sortWeight": 3,
"linkedProductId": null,
"version": 2
}
version 只在更新时必填。创建草稿允许内容暂时不完整,发布时必须满足:
- 标题、视频素材和有效封面完整。
- 视频素材为正常状态的
grassland_guideOSS MP4。 - 视频存在
ossUrl、thumbnailUrl和正数时长。 - 正文包含有效文字或正文图片。
管理端不再提交 publishTime、durationSeconds、relatedVideoIds:
durationSeconds从当前videoMaterialId对应的视频素材自动派生。publishTime在首次点击发布时由后端写入;后续编辑、下架和重上架都保留首次值。- 相关推荐在小程序查询时自动生成,没有其他候选也不阻止当前内容发布。
sortWeight数值越小越靠前,影响精选 Hero 位及相关内容同分时的弱排序。
后台详情返回新增字段:
{
"videoMaterialId": "2076195939797688322",
"videoUrl": "https://.../the-daily-dweebs-1080.mp4",
"customCoverMaterialId": "2076196060279070722",
"coverSource": "CUSTOM",
"effectiveCoverUrl": "https://.../cover.jpg",
"durationSeconds": 61,
"publishTime": "2026-07-12 14:44:56"
}
durationSeconds、publishTime 在 Knife4j 中均标记为只读。
所有雪花 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} |
视频详情 |
GET |
/mp/grassland-guide/videos/{videoId}/recommendations?page=1&pageSize=20 |
自动推荐分页,供滑动加载 |
分页规则:
page默认1,小于1时按1处理。pageSize默认20,最大100。- 只返回已发布内容,按发布时间倒序。
- 标准分页字段为
records、total、page、pageSize;当前响应不返回totalPages,需要时由前端计算。
详情直接返回 OSS 原视频地址:
{
"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。进入相关推荐区域时,请求独立分页接口:
{
"records": [
{
"videoId": "2076196084920610817",
"title": "草原指南测试|Caminandes 3:Llamigos",
"summary": "Blender 官方真实短片",
"coverUrl": "https://.../auto-cover.jpg",
"coverSource": "AUTO",
"durationSeconds": 151
}
],
"total": 2,
"page": 1,
"pageSize": 1
}
推荐排序规则:
- 仅使用
PUBLISHED内容,排除当前视频、已删除内容和重复项。 - 同一
linkedProductId(关联行程/产品)优先。 - 再按标题、摘要内容相似度排序。
featured、sortWeight、publishTime只做弱排序。- 其余已上架内容使用稳定随机顺序兜底。
候选集合不变时各页顺序稳定且不重不漏。若管理员恰好在用户翻页期间发布或下架内容,标准 offset 分页边界可能变化,前端应按 videoId 去重后追加。
小程序直接使用原生组件:
<video src="{{detail.videoUrl}}" controls></video>
OSS MP4 支持 Range 请求,原生 <video> 能从 MP4 元数据读取并显示当前播放时间、总时长和进度条。卡片时长使用接口的 durationSeconds 格式化即可,不做清晰度切换。
7. 测试环境验收结果
dev-v3 已于 2026-07-12 完成部署和真实数据验证:
hl-resource-service:8082/8182,Nacos2/2健康。hl-mp-service:8085/8185,Nacos2/2健康。hl-gateway:8080/8180,Nacos2/2健康。hl-user-service:8081/8181,Nacos2/2健康。- User Flyway
20260712.005执行成功;Resource Flyway20260712.001执行成功。 - Knife4j 已展示草原指南管理接口、素材上传接口及只读字段;小程序文档为上述 4 个 GET。
真实数据:
| 内容 | 素材 ID | 内容 ID | 时长 | 封面 |
|---|---|---|---|---|
| The Daily Dweebs | 2076195939797688322 |
2076196082752155649 |
61 秒 | CUSTOM |
| Caminandes 3: Llamigos | 2076195998660550657 |
2076196084920610817 |
151 秒 | AUTO |
| Glass Half | 2076196033146118145 |
2076196086652858370 |
194 秒 | AUTO |
| 呼伦贝尔草原有多大?(3812×2160 原片) | 2076248042813480962 |
2076248969276551170 |
83 秒 | CUSTOM |
验收结果:
- 三个文件都是不同的 Blender 官方真实作品,不是同一视频的多档清晰度。
- 三个视频均通过
STS_MULTIPART上传,分类为grassland_guide,存储方为OSS。 - 自定义封面通过
PRESIGNED_URL上传。 - MP4 Range 请求均返回
206 video/mp4。 - 自定义封面和 OSS 自动封面均返回
200 image/*。 - 小程序
pageSize=2实测分页为第 1 页 2 条、第 2 页 1 条,无重复。 - 三个详情均直接返回
videoUrl和正确时长,且不再包含relatedVideos。 - 三个推荐分页均为
total=2;按pageSize=1加载两页,无自身、无重复,重复请求顺序一致。 - 实测下架其中一条后推荐
total从 2 变为 1,候选中不含下架内容;重上架后恢复为 2。 - 故意提交伪造
publishTime=2000年、durationSeconds=9999和旧relatedVideoIds,后端均未采纳;下架重上架后首次发布时间保持不变。 - 旧
/play-auth接口已不可用。 - 真实 4K 原片为
412360919字节(约 393 MiB),通过STS_MULTIPART上传;OSSHEAD返回200、Content-Type: video/mp4、Content-Length: 412360919。 - 对 4K 原片请求
Range: bytes=0-1返回206、Content-Range: bytes 0-1/412360919,证明 OSS 支持原生<video>所需的分段读取,流量不经过业务服务器。 - 4K 内容使用独立上传的 JPEG 封面,详情返回
coverSource=CUSTOM;推荐分页返回 3 条上架内容且排除了当前视频。 - 当前测试桶默认域名同时返回
Content-Disposition: attachment和x-oss-force-download: true。HTTP Range 能正常工作,但小程序仍须做真机播放验收;若原生<video>因该响应头拒绝播放,优先给同一 OSS Bucket 绑定自定义域名直出,不需要先接 CDN。
8. 2026-07-14 正式环境 Nacos 部署清单
草原指南一期暂不使用 CDN、VOD、HLS 或转码,正式环境仅同步 OSS 原片上传上限。部署 hl-user-service 前执行:
- 在正式 Nacos 读取并备份完整
hl-user-service-prod.yml,确认生产实例实际加载的 namespace、group 和 dataId。 - 只做最小配置修改:
file:
max-size:
video: 2147483648
resumable-threshold: 10485760
- 不复制仓库内测试配置中的其他值,不覆盖正式环境已有的 OSS、数据库、Redis 或鉴权配置。
- 串行滚动部署
hl-user-service,确认两实例均注册健康且配置已生效。 - 用正式 OSS 做一轮大于 10 MiB 的 MP4 分片上传、确认、
HEAD和Range 206验收;不得在日志或交付文档中记录 STS、AccessKey、Token 等凭证。
9. 前端改造检查单
- 新增“草原指南管理”列表、编辑和发布页面。
- 素材库展示顶级分类“草原指南”,隐藏系统分类删除/停用/改码操作。
- 选择 MP4 后通过
loadedmetadata获取时长并Math.ceil,界面只读展示,不允许手填。 - 使用素材库通用上传接口处理
PRESIGNED_URL和STS_MULTIPART。 - 支持单独上传/选择图片作为自定义封面,并允许清空后恢复自动封面。
- 草原指南内容保存
videoMaterialId,不保存 VOD 字段。 - 发布日期只读:草稿显示未发布,首次发布成功后刷新详情回显后端时间。
- 删除管理员手工选择相关推荐的控件,不提交
relatedVideoIds。 - 小程序列表接入分页参数和分页结果。
- 小程序详情直接使用
videoUrl播放。 - 相关推荐通过独立分页接口滑动加载,并按
videoId去重追加。 - 删除 VOD SDK、
play-auth调用和清晰度切换 UI。 - 所有雪花 ID 均按字符串处理。