feat: harden content asset access

This commit is contained in:
Codex
2026-06-29 18:25:47 +08:00
parent ee578af1f6
commit 79d0d786a0
13 changed files with 762 additions and 67 deletions

View File

@@ -270,6 +270,37 @@ tenant:<tenantId>:theme
| 公共题库采纳/同步 | `GET /api/tenant-content/public-question-banks``POST /api/tenant-content/public-question-banks/adopt``POST /api/tenant-content/public-question-banks/sync``GET /api/tenant-content/public-question-banks/conflicts?adoptionId=...``POST /api/tenant-content/public-question-banks/conflicts/resolve``POST /api/tenant-content/public-question-banks/conflicts/resolve-batch` |
| 题库导出 | `POST /api/tenant-content/exports/questions``GET /api/tenant-content/exports/jobs` |
## 资料、PDF 和视频资源契约
前端必须把 `content_assets` 当成资源唯一台账。学生端资料、PDF 预览和题目视频播放都不能直接拼接私有 OSS/COS/Supabase Storage URL也不能把后台配置的 `cdnUrl` 持久缓存成长期可访问地址。
学生端资料流程:
1. 列表页调用 `GET /api/catalog/assets`,只展示后端返回的 active 资源。
2. 预览 PDF/图片时调用 `GET /api/catalog/assets/preview?assetId=...`
3. 下载资料时调用 `GET /api/catalog/assets/download?assetId=...`
4. 使用响应里的 `preview.url``download.url` 立即打开;不要写入本地长期缓存。
签名有效期规则:
- 学生 inline 预览、SVIP/会员资料、视频和资料包通常只有 300 秒左右有效期。
- 后台预览有效期也不是永久 URL租户后台应在用户点击时重新请求签名。
- 响应里的 `expiresInSec/expiresAt/signatureMode` 只用于 UI 提示和排查,不要自行延长有效期。
锁定资源 CDN 规则:
- `visibility=members/svip/private` 的外部 `cdnUrl` 默认会被后端拒绝,返回 `ASSET_CDN_ACCESS_NOT_ALLOWED`
- 只有后台明确登记 `metadata.providerManagedAccess=true``metadata.cdnAccessMode='signed_by_provider'`,后端才允许把外部 URL 作为 provider-managed 资源返回。
- 商用环境更推荐把锁定资料登记为 `objectKey`,由后端生成 OSS/COS/Supabase Storage 私有签名 URL。
租户后台排查:
```text
GET /api/tenant-content/assets/access-events?assetId=<assetId>&limit=100
```
该接口返回资源访问事件,包括学生下载、学生预览、后台下载、后台预览、上传签名、上传确认以及 denied 原因。租户后台可以在资源详情页增加“访问记录/异常记录”面板。
## 练习访问控制契约
前端不要先拉完整题目列表再自行判断免费额度。用户点击顺序刷题、随机刷题、全真模拟时,统一调用 `POST /api/learning/practice-sessions`,后端会根据 `content_entries.accessRules``content_nodes.accessRules``question_collections.accessRules``practice_blueprints.accessRules` 和当前用户权益决定最终题目快照。