forked from wangziqi/gongxue-base
feat: protect video playback access
This commit is contained in:
@@ -20,6 +20,8 @@ apps/api/src/
|
||||
learning/ 组卷 session、答题、错题、收藏、练习进度
|
||||
commerce/ 订单、支付确认、激活码、权益
|
||||
referral/ 销售/代理客资追踪、首绑保护、团队关系、CRM 队列
|
||||
storage/ 对象存储签名 provider
|
||||
video/ 题目视频列表、搜索、SVIP/次数校验和签名播放
|
||||
platform-admin/ 平台方 SaaS 租户、订阅、账单、使用量
|
||||
tenant-admin/ 租户品牌、域名、公开设置、登录/商户配置、成员权限、活动/兑换码运营
|
||||
tenant-content/ 租户后台内容维护:入口、分类树、集合、练习蓝图、题目、视频、分数线、单词、知识手册、资料资源、批量导入
|
||||
|
||||
@@ -69,8 +69,8 @@
|
||||
| 知识手册目录/内容 | 可联调 | `/api/catalog/handbook-*` |
|
||||
| 知识手册 JSON 导入 | 可联调 | `/api/tenant-content/imports/*/handbook` |
|
||||
| 分数线字段/院校/专业/记录/趋势 | 可联调 | `/api/scoreline/*` |
|
||||
| 题目视频/批量预加载/搜索 | 可联调 | `/api/questions/*/videos`、`/api/videos/search` |
|
||||
| 视频会员播放次数 | 待补齐 | 缺播放次数扣减、播放日志、防盗链、水印 |
|
||||
| 题目视频/批量预加载/搜索 | 可联调 | `/api/questions/*/videos`、`/api/videos/search`;付费视频列表不返回可播放 URL |
|
||||
| 视频会员播放次数 | 可联调 | `POST /api/videos/play` 支持 SVIP/视频次数校验、签名播放、次数扣减、播放日志;深度防盗链和动态水印继续补 |
|
||||
|
||||
## 资料与对象存储
|
||||
|
||||
|
||||
@@ -76,6 +76,7 @@ GET /api/scoreline/years
|
||||
GET /api/questions/{questionId}/videos
|
||||
POST /api/questions/videos/batch
|
||||
GET /api/videos/search
|
||||
POST /api/videos/play
|
||||
GET /api/tenant-content/content-entries
|
||||
PUT /api/tenant-content/content-entries
|
||||
GET /api/tenant-content/content-nodes
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
| 全真模拟 | `components/AdminMockexam`、`MockExamConfigModal.tsx` | 部分覆盖 | 后端有 blueprint 基础;缺完整交卷报告、排名、复盘 |
|
||||
| 错题本 | 用户 stats/错题逻辑 | 已覆盖 | 后续补错题复习计划 |
|
||||
| 收藏夹 | `WordFavoritesPage.tsx`、题目收藏 | 已覆盖 | 题目和单词收藏已有 |
|
||||
| 题目视频 | `VideoPlayer.tsx` | 部分覆盖 | 题目视频查询已有;缺播放签名、次数扣减、水印、防下载 |
|
||||
| 题目视频 | `VideoPlayer.tsx` | 部分覆盖 | 题目视频查询、播放签名、SVIP/次数扣减、播放日志已有;缺深度防盗链、动态水印、播放统计报表 |
|
||||
| 背单词 | `VocabularyPage.tsx`、`VocabularyQuiz.tsx` | 部分覆盖 | 单词列表/进度/收藏/统计已有;缺完整艾宾浩斯算法、每日计划、收藏练习细节 |
|
||||
| 知识手册 | `Handbook*.tsx` | 已覆盖 | 前端需做好 Markdown/公式/图片渲染和搜索体验 |
|
||||
| 分数线 | `ScorelinePage.tsx` | 已覆盖 | 动态字段/趋势已有;缺批量导入和复杂筛选优化 |
|
||||
@@ -97,7 +97,7 @@
|
||||
### P1:商用主链路
|
||||
|
||||
1. 微信/支付宝支付和 webhook 幂等。
|
||||
2. 对象存储 PDF 预览、视频播放签名、防盗链、水印。
|
||||
2. 对象存储 PDF 预览、视频深度防盗链、动态水印。
|
||||
3. Excel/CSV、分数线、视频批量导入。
|
||||
4. 数据看板和销售/代理分佣结算。
|
||||
5. 公共题库授权、租户采纳和版本同步。
|
||||
@@ -108,4 +108,3 @@
|
||||
2. 班级、教师、学生分组和学习督导。
|
||||
3. AI 择校推荐和 PDF 报告。
|
||||
4. 题库导出、试卷生成、每日一练运营工具。
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
- Supabase/PostgreSQL 多租户 schema、RLS、索引、触发器。
|
||||
- Node.js API 分层:`core/features`。
|
||||
- 学生端核心 API:题库、练习、答题、错题、收藏、背单词、知识手册、分数线、视频、资料、订单、权益、个人中心。
|
||||
- 学生端核心 API:题库、练习、答题、错题、收藏、背单词、知识手册、分数线、视频播放签名、资料、订单、权益、个人中心。
|
||||
- 租户后台 API:品牌、域名、设置、支付账户、登录 provider、私密密钥、活动、激活码、优惠券、成员权限、审计、内容管理。
|
||||
- 平台后台 API:租户、SaaS 套餐、订阅、账单、服务费收款、用量。
|
||||
- 销售/代理/CRM 增长链路:邀请码、扫码事件、首绑保护、团队、统计、CRM 队列。
|
||||
@@ -33,7 +33,7 @@
|
||||
|
||||
2. 对象存储
|
||||
- 已接阿里云 OSS、腾讯云 COS、Supabase Storage 的上传/下载签名 provider。
|
||||
- 继续补上传后对象存在性校验、PDF 预览地址、视频播放签名、防盗链、水印和 worker 校验。
|
||||
- 继续补上传后对象存在性校验、PDF 预览地址、视频深度防盗链、动态水印和 worker 校验。
|
||||
- `content_assets` 继续作为资源台账,不允许前端绕过台账直接访问私有资源。
|
||||
|
||||
3. 真实导入 dry-run
|
||||
@@ -71,8 +71,8 @@
|
||||
- 租户采纳、复制、授权、版本同步策略。
|
||||
|
||||
5. 视频会员控制
|
||||
- 视频 SVIP 权限、播放次数扣减。
|
||||
- 防盗链、水印、播放日志、播放统计。
|
||||
- 已完成视频 SVIP 权限、播放次数扣减、签名播放和播放日志。
|
||||
- 继续补深度防盗链、动态水印、播放统计。
|
||||
- 单题视频和通用知识视频混合推荐。
|
||||
|
||||
6. 学习统计
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
## 目标
|
||||
|
||||
题库里的图片、PDF、视频、音频、资料包等媒体资源统一走 `content_assets` 台账和后端签名接口。前端不直接保存或读取云厂商密钥,也不直接拼接私有资源 URL。
|
||||
题库里的图片、PDF、视频、音频、资料包等媒体资源统一走 `content_assets` 台账和后端签名接口。前端不直接保存或读取云厂商密钥,也不直接拼接私有资源 URL。题目视频播放还需要经过 `POST /api/videos/play` 校验 SVIP 或视频次数权益后下发短期签名 URL。
|
||||
|
||||
已接入的 provider:
|
||||
|
||||
@@ -34,6 +34,12 @@ PUT /api/tenant-content/assets
|
||||
GET /api/catalog/assets/download?assetId=...
|
||||
```
|
||||
|
||||
学生端视频播放:
|
||||
|
||||
```text
|
||||
POST /api/videos/play
|
||||
```
|
||||
|
||||
后台管理员下载:
|
||||
|
||||
```text
|
||||
@@ -46,7 +52,7 @@ POST /api/tenant-content/assets/sign-download
|
||||
- `objectKey` 默认必须以当前 `tenantId/` 开头,防止跨租户覆盖或读取。
|
||||
- 禁止 `..`、反斜杠、编码斜杠等危险 object key。
|
||||
- 上传会校验 MIME 类型和文件大小。
|
||||
- 下载必须先经过 API 权限判断,再下发短期签名 URL。
|
||||
- 下载和视频播放必须先经过 API 权限判断,再下发短期签名 URL。
|
||||
- 云厂商 AccessKey、SecretKey、Service Role Key 只存在服务端环境变量,不返回前端。
|
||||
- `content_assets` 是资源唯一台账,前端不得绕过台账直接访问私有 bucket。
|
||||
|
||||
@@ -98,7 +104,7 @@ SUPABASE_STORAGE_SERVICE_KEY=
|
||||
- bucket 默认私有,公开资源也建议先经过 CDN/防盗链策略,不让前端直接持有写权限。
|
||||
- 图片、PDF、视频分别设置合理的 CORS,只允许前端域名和小程序业务域名访问。
|
||||
- 开启对象版本控制、生命周期、跨区域复制或定时备份,满足后续容灾要求。
|
||||
- 视频资源建议后续接入转码、水印、防盗链、播放日志和播放次数扣减。
|
||||
- 视频资源已接入 SVIP/播放次数校验、短期签名和播放日志;生产阶段继续补转码、动态水印、CDN 防盗链和播放统计。
|
||||
- 大文件上传后应由 worker 校验对象是否真实存在、大小/hash 是否匹配,再把资源状态从 `draft` 发布为 `active`。
|
||||
|
||||
## 官方依据
|
||||
|
||||
@@ -139,7 +139,7 @@ tenant:<tenantId>:theme
|
||||
| 提交答案 | `POST /api/learning/answers` |
|
||||
| 错题本 | `GET /api/learning/wrong-questions`、`POST /api/learning/wrong-questions/resolve` |
|
||||
| 收藏夹 | `GET/POST /api/learning/favorites/questions` |
|
||||
| 题目视频 | `GET /api/questions/{questionId}/videos`、`POST /api/questions/videos/batch` |
|
||||
| 题目视频 | `GET /api/questions/{questionId}/videos`、`POST /api/questions/videos/batch`、`POST /api/videos/play` |
|
||||
| 背单词 | `/api/catalog/vocabulary-units`、`/api/catalog/vocabulary-words` |
|
||||
| 单词进度 | `/api/learning/vocabulary/progress`、`/api/learning/vocabulary/stats` |
|
||||
| 单词收藏 | `/api/learning/vocabulary/favorites` |
|
||||
@@ -152,6 +152,60 @@ tenant:<tenantId>:theme
|
||||
| 个人中心 | `GET/PATCH /api/profile/me` |
|
||||
| 销售分享 | `/api/referral/resolve`、`track-event`、`bind` |
|
||||
|
||||
## 视频播放契约
|
||||
|
||||
题目视频分为 `free`、`svip`、`video_quota` 三种访问模式。列表接口只用于展示标题、封面、时长、访问模式和试看秒数;除免费公开视频外,列表和搜索接口不会返回可播放 URL。
|
||||
|
||||
播放步骤:
|
||||
|
||||
1. 进入题目页后调用 `GET /api/questions/{questionId}/videos` 或批量预加载 `POST /api/questions/videos/batch`。
|
||||
2. 用户点击播放时调用 `POST /api/videos/play`。
|
||||
3. 后端校验当前 session 用户、租户、题目绑定关系、SVIP 权益或视频次数权益。
|
||||
4. 后端返回短期签名 URL、播放 token、权益来源和过期时间。
|
||||
5. 前端播放器只使用本次返回的 `playback.url`,不要缓存为长期资源地址。
|
||||
|
||||
请求示例:
|
||||
|
||||
```json
|
||||
{
|
||||
"videoId": "00000000-0000-0000-0000-000000000821",
|
||||
"questionId": "00000000-0000-0000-0000-000000000401"
|
||||
}
|
||||
```
|
||||
|
||||
响应关键字段:
|
||||
|
||||
```json
|
||||
{
|
||||
"item": {
|
||||
"id": "...",
|
||||
"title": "...",
|
||||
"accessMode": "svip",
|
||||
"freePreviewSeconds": 15
|
||||
},
|
||||
"playToken": "vp_...",
|
||||
"playback": {
|
||||
"url": "https://...",
|
||||
"expiresAt": "2026-06-28T12:00:00.000Z",
|
||||
"signatureMode": "signed"
|
||||
},
|
||||
"access": {
|
||||
"mode": "svip",
|
||||
"entitlementId": "...",
|
||||
"quotaAccountId": null,
|
||||
"consumedQuota": 0
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
前端处理规则:
|
||||
|
||||
- `VIDEO_SVIP_REQUIRED`:弹出开通或升级会员。
|
||||
- `VIDEO_QUOTA_REQUIRED`:提示购买视频次数包或套餐。
|
||||
- `VIDEO_ASSET_REQUIRED`:展示“视频暂不可播放”,同时上报前端日志。
|
||||
- 签名 URL 过期后必须重新调用 `/api/videos/play`,不要重试旧 URL。
|
||||
- 小程序/H5 不保存对象存储真实 key,不把播放 URL 写入本地持久缓存。
|
||||
|
||||
## 题库新模型接入方式
|
||||
|
||||
旧项目常按“地区 -> 科目 -> 章节/试卷”固定层级处理。新项目不要写死层级,按下面模型渲染:
|
||||
|
||||
Reference in New Issue
Block a user