# 对象存储接入说明 更新时间: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/Word 导出 worker: ```bash npm --workspace @tiku-saas/worker run exports: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`。 ## 题库导出生成资源 题库导出 PDF/Word 不走前端上传,而是由后端和 worker 自动进入资源台账: 1. 租户后台调用 `POST /api/tenant-content/exports/questions`,`format=pdf` 或 `format=docx`。 2. API 校验租户内容权限、导出范围和答案/解析开关,创建 `content_export_jobs.status=pending`。 3. `apps/worker --job exports` 抢占 pending job,复用后端导出 payload 规则渲染 PDF/Word,应用 `options.watermarkText` 水印。 4. worker 将二进制文件写入配置的对象存储。`local_dev` 会写入 `EXPORT_LOCAL_STORAGE_ROOT`,生产建议使用 `supabase_storage`、`aliyun_oss` 或 `tencent_cos`。 5. worker 创建 `content_assets`,设置 `uploadStatus=verified`,并把 `assetId`、文件名、大小和 SHA-256 回填到 `content_export_jobs.output_metadata`。 6. 前端轮询 `GET /api/tenant-content/exports/jobs`,拿到 `assetId` 后通过 `POST /api/tenant-content/assets/sign-download` 或 `sign-preview` 下载/预览。 注意:PDF/Word 文件本身不直接存入数据库,数据库只保存 job、资源台账和校验 metadata。 确认上传示例: ```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、服务端权限控制和资源台账。