Files
gongxue-base/docs/refactor/object-storage.md
2026-06-29 06:14:58 +08:00

185 lines
7.1 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 对象存储接入说明
更新时间2026-06-29
## 目标
题库里的图片、PDF、视频、音频、资料包等媒体资源统一走 `content_assets` 台账和后端签名接口。前端不直接保存或读取云厂商密钥,也不直接拼接私有资源 URL。题目视频播放还需要经过 `POST /api/videos/play` 校验 SVIP 或视频次数权益后下发短期签名 URL。
已接入的 provider
- `local_dev`:本地开发占位签名,便于前后端联调。
- `aliyun_oss`:阿里云 OSS 官方 Node.js SDK `signatureUrl`
- `tencent_cos`:腾讯云 COS XML API V5 签名 URL服务端用 Node `crypto` 实现,避免引入当前 COS Node SDK 的高危依赖。
- `supabase_storage`Supabase Storage 官方 `createSignedUploadUrl` / `createSignedUrl` 语义。
- `external_url`:外部公开或厂商托管 URL只允许作为已管理资源的下载地址不支持后端直传签名。
## API
后台申请上传签名:
```text
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
POST /api/videos/play
```
后台管理员下载:
```text
POST /api/tenant-content/assets/sign-download
```
后台管理员预览:
```text
POST /api/tenant-content/assets/sign-preview
```
后台资源复检 worker
```bash
npm --workspace @tiku-saas/worker run assets:once
```
## 标准上传流程
后台前端上传 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` 的托管对象资源。
7. 生产环境定时运行 assets worker复检 `pending/verified` 托管对象的大小、MIME、SHA-256 等元数据。
8. 如果复检发现对象丢失、跨租户 objectKey、大小/MIME/checksum 不一致worker 会把资源置为 `uploadStatus=failed`,并将 `active` 资源退回 `draft`,同时写入 `security_flags.assetRecheckFailed=true``audit_logs`
确认上传示例:
```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`
- worker 的复检证据写入 `verification_details.assetWorker`,包含 `lastCheckedAt``lastResult``expected``observed``issues`,便于租户后台定位资源异常。
## 安全规则
- 只有租户内容维护权限用户可以申请上传签名。
- `objectKey` 默认必须以当前 `tenantId/` 开头,防止跨租户覆盖或读取。
- 禁止 `..`、反斜杠、编码斜杠等危险 object key。
- 上传会校验 MIME 类型和文件大小。
- 托管对象资源未确认前不能发布为 `active`,学生端不可下载。
- PDF/图片预览使用 `inline` 签名,不等同于长期公开 URL。
- 下载和视频播放必须先经过 API 权限判断,再下发短期签名 URL。
- 云厂商 AccessKey、SecretKey、Service Role Key 只存在服务端环境变量,不返回前端。
- `content_assets` 是资源唯一台账,前端不得绕过台账直接访问私有 bucket。
- 前端不能把 `uploadStatus=failed``status=draft` 的资源继续展示为可下载;列表仍返回时应展示“资料处理中”或“资源异常已下架”,真正下载/预览会被后端拒绝。
## 环境变量
通用:
```text
STORAGE_DEFAULT_PROVIDER=local_dev
STORAGE_DEFAULT_BUCKET=tenant-assets
STORAGE_PUBLIC_BASE_URL=
STORAGE_MAX_UPLOAD_BYTES=524288000
STORAGE_ALLOWED_MIME_PREFIXES=image/,video/,audio/
STORAGE_ALLOWED_MIME_TYPES=application/pdf,application/json,application/zip,application/x-zip-compressed,application/msword,application/vnd.openxmlformats-officedocument.wordprocessingml.document,application/vnd.ms-excel,application/vnd.openxmlformats-officedocument.spreadsheetml.sheet,application/vnd.ms-powerpoint,application/vnd.openxmlformats-officedocument.presentationml.presentation,application/octet-stream,text/plain,text/markdown,text/csv
STORAGE_REQUIRE_TENANT_PREFIX=true
```
阿里云 OSS
```text
ALIYUN_OSS_REGION=oss-cn-hangzhou
ALIYUN_OSS_ENDPOINT=
ALIYUN_OSS_ACCESS_KEY_ID=
ALIYUN_OSS_ACCESS_KEY_SECRET=
ALIYUN_OSS_STS_TOKEN=
ALIYUN_OSS_INTERNAL=false
```
腾讯云 COS
```text
TENCENT_COS_REGION=ap-shanghai
TENCENT_COS_APP_ID=
TENCENT_COS_SECRET_ID=
TENCENT_COS_SECRET_KEY=
TENCENT_COS_SECURITY_TOKEN=
```
Supabase Storage
```text
SUPABASE_STORAGE_URL=https://your-project.supabase.co/storage/v1
SUPABASE_STORAGE_SERVICE_KEY=
```
assets worker
```text
WORKER_ASSET_BATCH_SIZE=50
WORKER_ASSET_MIN_AGE_SECONDS=300
WORKER_ASSET_RECHECK_INTERVAL_SECONDS=86400
WORKER_ASSET_REQUEST_TIMEOUT_MS=10000
```
## 生产建议
- 阿里云和腾讯云生产环境优先用 STS/临时密钥或 RAM/CAM 最小权限账号。
- bucket 默认私有,公开资源也建议先经过 CDN/防盗链策略,不让前端直接持有写权限。
- 图片、PDF、视频分别设置合理的 CORS只允许前端域名和小程序业务域名访问。
- 开启对象版本控制、生命周期、跨区域复制或定时备份,满足后续容灾要求。
- 视频资源已接入 SVIP/播放次数校验、短期签名和播放日志生产阶段继续补转码、动态水印、CDN 防盗链和播放统计。
- 大文件上传已经支持 API 即时确认和 worker 元数据复检;后续继续补杀毒、转码、水印和 CDN 刷新。
## 官方依据
- 阿里云 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、服务端权限控制和资源台账。