forked from wangziqi/gongxue-base
feat: verify content asset uploads
This commit is contained in:
@@ -91,8 +91,9 @@
|
||||
| 阿里云 OSS 签名 | 可联调 | `aliyun_oss` provider |
|
||||
| 腾讯 COS 签名 | 可联调 | `tencent_cos` provider |
|
||||
| Supabase Storage 签名 | 可联调 | `supabase_storage` provider |
|
||||
| PDF 预览/防盗链/水印 | 待补齐 | 商用上线前补齐 |
|
||||
| 上传后对象校验 | 待补齐 | 需 worker 或 API 回调确认 size/hash/mime |
|
||||
| 上传后对象校验 | 可联调 | `/api/tenant-content/assets/confirm-upload`;托管对象必须 verified 后才能发布/下载 |
|
||||
| PDF/图片预览签名 | 可联调 | `/api/catalog/assets/preview`、`/api/tenant-content/assets/sign-preview`;使用 inline 短期签名 |
|
||||
| 深度防盗链/水印/杀毒 | 待补齐 | 商用上线前补 worker、CDN 防盗链、动态水印和安全扫描 |
|
||||
|
||||
## 订单、会员、营销
|
||||
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
| 分数线 | `ScorelinePage.tsx` | 已覆盖 | 动态字段/趋势已有;缺批量导入和复杂筛选优化 |
|
||||
| 商城/SVIP | `Store.tsx`、`SvipModal.tsx` | 部分覆盖 | 套餐、订单、订单详情/状态轮询、权益、激活码预检查/兑换、优惠券领取/下单抵扣、微信支付/支付宝 provider 主链路已有;缺退款/对账/补偿任务和前端收银台体验 |
|
||||
| 个人中心 | `Profile.tsx` | 部分覆盖 | 基本资料、权益、订单统计、练习历史、学习统计、签到积分、考试倒计时和趋势已有;缺勋章 API、账号绑定/换绑、学习报告可视化 |
|
||||
| 资料下载 | `QuestionExporterPublishModal.tsx` 等 | 部分覆盖 | 资源台账/签名下载已有;缺 PDF 预览、水印、防盗链和上传后对象校验 |
|
||||
| 资料下载 | `QuestionExporterPublishModal.tsx` 等 | 部分覆盖 | 资源台账、上传确认、签名下载和 PDF/图片预览基础已有;缺水印、防盗链、杀毒扫描和 worker 复检 |
|
||||
| AI 择校推荐 | 业务规划新增 | 未覆盖 | 需设计学生输入 schema、地区数据上下文、AI JSON 输出、PDF 报告 |
|
||||
| 题目反馈 | `02-API接口.md` 用户反馈 | 部分覆盖 | 学生提交、本人列表、租户后台处理、状态事件、反馈奖励积分已覆盖;缺处理通知、前端消息提醒和批量统计 |
|
||||
| 签到积分 | `Profile.tsx`、`02-API接口.md` | 部分覆盖 | 每日签到、连续签到基础、积分流水、重复签到幂等已覆盖;缺积分兑换、活动任务和更完整的运营规则 |
|
||||
@@ -66,7 +66,7 @@
|
||||
| 分数线维护 | 已覆盖 | 字段/院校/专业/记录 CRUD 已有 |
|
||||
| 视频维护/绑定 | 已覆盖 | video CRUD 和 question-video 绑定已有 |
|
||||
| CRM 配置和队列 | 部分覆盖 | 配置/队列已有;钉钉/飞书/企微真实发送 worker、签名、重试、死信待补 |
|
||||
| 对象存储配置 | 部分覆盖 | 系统 env provider 已有;租户级存储策略、上传后校验待补 |
|
||||
| 对象存储配置 | 部分覆盖 | 系统 env provider、上传签名、上传确认和预览下载签名已有;租户级存储策略、CDN/水印/杀毒待补 |
|
||||
|
||||
## 平台 SaaS 后台功能
|
||||
|
||||
|
||||
@@ -40,7 +40,8 @@
|
||||
|
||||
2. 对象存储
|
||||
- 已接阿里云 OSS、腾讯云 COS、Supabase Storage 的上传/下载签名 provider。
|
||||
- 继续补上传后对象存在性校验、PDF 预览地址、视频深度防盗链、动态水印和 worker 校验。
|
||||
- 已补上传后对象确认接口、托管对象发布前 verified 校验、PDF/图片 inline 预览签名。
|
||||
- 继续补视频深度防盗链、动态水印、worker 复检、杀毒扫描、CDN 刷新和对象生命周期策略。
|
||||
- `content_assets` 继续作为资源台账,不允许前端绕过台账直接访问私有资源。
|
||||
|
||||
3. 真实导入 dry-run
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# 对象存储接入说明
|
||||
|
||||
更新时间:2026-06-22
|
||||
更新时间:2026-06-29
|
||||
|
||||
## 目标
|
||||
|
||||
@@ -22,18 +22,30 @@
|
||||
POST /api/tenant-content/assets/sign-upload
|
||||
```
|
||||
|
||||
后台登记资源:
|
||||
后台登记资源草稿:
|
||||
|
||||
```text
|
||||
PUT /api/tenant-content/assets
|
||||
```
|
||||
|
||||
后台确认上传并发布:
|
||||
|
||||
```text
|
||||
POST /api/tenant-content/assets/confirm-upload
|
||||
```
|
||||
|
||||
学生端下载:
|
||||
|
||||
```text
|
||||
GET /api/catalog/assets/download?assetId=...
|
||||
```
|
||||
|
||||
学生端预览:
|
||||
|
||||
```text
|
||||
GET /api/catalog/assets/preview?assetId=...
|
||||
```
|
||||
|
||||
学生端视频播放:
|
||||
|
||||
```text
|
||||
@@ -46,12 +58,51 @@ POST /api/videos/play
|
||||
POST /api/tenant-content/assets/sign-download
|
||||
```
|
||||
|
||||
后台管理员预览:
|
||||
|
||||
```text
|
||||
POST /api/tenant-content/assets/sign-preview
|
||||
```
|
||||
|
||||
## 标准上传流程
|
||||
|
||||
后台前端上传 PDF、图片、视频或资料包时必须走下面流程:
|
||||
|
||||
1. 调用 `POST /api/tenant-content/assets/sign-upload` 申请短期上传 URL。
|
||||
2. 前端使用返回的 `upload.url` 和 `upload.headers` 直传对象存储。
|
||||
3. 调用 `PUT /api/tenant-content/assets` 登记资源台账。托管对象默认进入 `status=draft`、`uploadStatus=pending`。
|
||||
4. 调用 `POST /api/tenant-content/assets/confirm-upload`,由后端读取对象元数据并比对大小、MIME、SHA-256。
|
||||
5. 校验通过且 `publish=true` 时,后端将资源置为 `status=active`、`uploadStatus=verified`。
|
||||
6. 学生端只能下载或预览 `active + verified` 的托管对象资源。
|
||||
|
||||
确认上传示例:
|
||||
|
||||
```json
|
||||
{
|
||||
"assetId": "00000000-0000-0000-0000-000000000000",
|
||||
"fileSizeBytes": 4096,
|
||||
"mimeType": "application/pdf",
|
||||
"checksumSha256": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
|
||||
"publish": true
|
||||
}
|
||||
```
|
||||
|
||||
注意:
|
||||
|
||||
- `local_dev` 用声明的元数据模拟对象 HEAD,方便本地和集成测试。
|
||||
- `aliyun_oss` 使用 OSS 对象 HEAD 元数据。
|
||||
- `tencent_cos` 使用 COS HEAD Object 预签名请求读取元数据。
|
||||
- `supabase_storage` 使用 Storage list metadata 做最小存在性/大小校验。
|
||||
- 如果 provider 无法返回 SHA-256,后端会记录 `checksumUnavailable=true`,生产建议上传时写入对象自定义元数据,例如 `x-oss-meta-sha256` 或 `x-cos-meta-sha256`。
|
||||
|
||||
## 安全规则
|
||||
|
||||
- 只有租户内容维护权限用户可以申请上传签名。
|
||||
- `objectKey` 默认必须以当前 `tenantId/` 开头,防止跨租户覆盖或读取。
|
||||
- 禁止 `..`、反斜杠、编码斜杠等危险 object key。
|
||||
- 上传会校验 MIME 类型和文件大小。
|
||||
- 托管对象资源未确认前不能发布为 `active`,学生端不可下载。
|
||||
- PDF/图片预览使用 `inline` 签名,不等同于长期公开 URL。
|
||||
- 下载和视频播放必须先经过 API 权限判断,再下发短期签名 URL。
|
||||
- 云厂商 AccessKey、SecretKey、Service Role Key 只存在服务端环境变量,不返回前端。
|
||||
- `content_assets` 是资源唯一台账,前端不得绕过台账直接访问私有 bucket。
|
||||
@@ -105,10 +156,10 @@ SUPABASE_STORAGE_SERVICE_KEY=
|
||||
- 图片、PDF、视频分别设置合理的 CORS,只允许前端域名和小程序业务域名访问。
|
||||
- 开启对象版本控制、生命周期、跨区域复制或定时备份,满足后续容灾要求。
|
||||
- 视频资源已接入 SVIP/播放次数校验、短期签名和播放日志;生产阶段继续补转码、动态水印、CDN 防盗链和播放统计。
|
||||
- 大文件上传后应由 worker 校验对象是否真实存在、大小/hash 是否匹配,再把资源状态从 `draft` 发布为 `active`。
|
||||
- 大文件上传已经支持 API 即时确认;后续可增加 worker 做异步复检、杀毒、转码、水印和 CDN 刷新。
|
||||
|
||||
## 官方依据
|
||||
|
||||
- 阿里云 OSS Node.js SDK 支持通过 `signatureUrl` 为上传或下载生成带过期时间的签名 URL。
|
||||
- 腾讯云 COS XML API V5 签名由 `q-sign-algorithm`、`q-ak`、`q-sign-time`、`q-key-time`、`q-header-list`、`q-url-param-list`、`q-signature` 等字段组成,可用于预签名 URL。
|
||||
- Supabase Storage 提供 `createSignedUploadUrl` 和 `createSignedUrl`,分别用于签名上传和签名下载;私有 bucket 仍应配合 RLS 和服务端权限控制。
|
||||
- 阿里云 OSS Node.js SDK 支持通过 `signatureUrl` 为上传/下载生成带过期时间的签名 URL,并可通过对象 HEAD 读取元数据。
|
||||
- 腾讯云 COS XML API V5 签名由 `q-sign-algorithm`、`q-ak`、`q-sign-time`、`q-key-time`、`q-header-list`、`q-url-param-list`、`q-signature` 等字段组成,可用于预签名 URL 和 HEAD Object。
|
||||
- Supabase Storage 提供 `createSignedUploadUrl` 和 `createSignedUrl`,分别用于签名上传和签名下载;私有 bucket 仍应配合 RLS、服务端权限控制和资源台账。
|
||||
|
||||
@@ -167,7 +167,7 @@ tenant:<tenantId>:theme
|
||||
| 单词收藏 | `/api/learning/vocabulary/favorites` |
|
||||
| 知识手册 | `/api/catalog/handbook-subjects`、`handbook-chapters`、`handbook-entries` |
|
||||
| 分数线 | `/api/scoreline/fields`、`schools`、`majors`、`records`、`trend`、`years` |
|
||||
| 资料下载 | `/api/catalog/assets`、`/api/catalog/assets/download` |
|
||||
| 资料下载/预览 | `/api/catalog/assets`、`/api/catalog/assets/preview`、`/api/catalog/assets/download` |
|
||||
| 商城 | `/api/catalog/svip-plans`、`POST /api/commerce/coupons/claim`、`POST /api/commerce/orders`、`POST /api/commerce/payments/create` |
|
||||
| 订单/权益 | `/api/commerce/orders`、`/api/commerce/orders/detail`、`/api/commerce/orders/status`、`/api/commerce/entitlements` |
|
||||
| 激活码 | `POST /api/commerce/activation-codes/check`、`POST /api/commerce/activation-codes/redeem` |
|
||||
@@ -438,6 +438,57 @@ GET /api/learning/vocabulary/review-plan?unitId=<unitId>&reviewLimit=30&newLimit
|
||||
- 签名 URL 过期后必须重新调用 `/api/videos/play`,不要重试旧 URL。
|
||||
- 小程序/H5 不保存对象存储真实 key,不把播放 URL 写入本地持久缓存。
|
||||
|
||||
## 资料上传、预览和下载契约
|
||||
|
||||
学生端资料只读取目录、预览和下载签名,不接触对象存储真实密钥,也不自行拼接私有 bucket 地址。
|
||||
|
||||
学生端展示资料列表:
|
||||
|
||||
```http
|
||||
GET /api/catalog/assets?assetType=pdf®ionId=<regionId>&includeLocked=true
|
||||
```
|
||||
|
||||
学生端 PDF/图片预览:
|
||||
|
||||
```http
|
||||
GET /api/catalog/assets/preview?assetId=<assetId>
|
||||
```
|
||||
|
||||
学生端下载:
|
||||
|
||||
```http
|
||||
GET /api/catalog/assets/download?assetId=<assetId>
|
||||
```
|
||||
|
||||
前端处理规则:
|
||||
|
||||
- `preview.url` 是短期 inline URL,只给预览组件使用,不持久化。
|
||||
- `download.url` 是短期 attachment URL,只给下载动作使用。
|
||||
- `ASSET_SVIP_REQUIRED`:提示开通对应地区/科目权益。
|
||||
- `ASSET_UPLOAD_NOT_VERIFIED`:展示“资料正在处理中”,并上报前端日志。
|
||||
- `ASSET_PREVIEW_NOT_SUPPORTED`:隐藏预览按钮,仅保留下载或提示不支持预览。
|
||||
- `previewUrl` 字段只作为公开/托管预览提示,不代表可以绕过接口直接访问。
|
||||
|
||||
租户后台上传资料必须走五步:
|
||||
|
||||
```text
|
||||
sign-upload -> 直传对象存储 -> PUT assets 登记草稿 -> confirm-upload -> sign-preview 验收
|
||||
```
|
||||
|
||||
后台上传确认:
|
||||
|
||||
```json
|
||||
{
|
||||
"assetId": "<assetId>",
|
||||
"fileSizeBytes": 4096,
|
||||
"mimeType": "application/pdf",
|
||||
"checksumSha256": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
|
||||
"publish": true
|
||||
}
|
||||
```
|
||||
|
||||
托管对象在确认前会保持 `status=draft`、`uploadStatus=pending`,学生端不会看到。确认失败时后端返回 `UPLOAD_VERIFICATION_FAILED`,后台必须展示失败原因并允许重新上传,不能前端强行改为已发布。
|
||||
|
||||
## 考试倒计时、签到积分和反馈
|
||||
|
||||
首页可用 `GET /api/catalog/exam-dates?regionId=<regionId>` 展示地区公开考试日期;个人中心优先用 `GET /api/profile/exam-countdowns`,后端会按学生当前 `regionId/selectedSchoolId` 返回匹配倒计时。
|
||||
|
||||
Reference in New Issue
Block a user