Files

77 lines
6.9 KiB
Markdown
Raw Permalink 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.
# 前端直传腾讯云 COS(视频大文件)
## Goal
让管理后台的视频上传绕开 PHP 服务器的中转通道,由浏览器直接把视频文件分片上传到腾讯云 COS。
解决两类痛点:(1) 服务器双倍带宽消耗(client→server→COS);(2) PHP `upload_max_filesize` / `post_max_size` 与 fpm 进程内存对大视频不友好。
首发场景:诊单 `CallRecordPanel.vue` 上传通话回放视频。
## What I already know
- 现有上传链路:`admin/src/components/upload/index.vue``POST /upload/video``UploadController::video`)→ `UploadService::video()``storage/Driver``engine/Qcloud::upload()``putObject`
- 服务端入参依赖 `$_FILES`,受 PHP 上传上限约束;返回 `{id, cid, type, name, uri, url}``uri``FileService::getFileUrl()` 拼接 OSS/CDN 域名。
- 存储驱动配置走 `dev_config.storage`(管理员可在后台「存储配置」切换 local/qiniu/aliyun/qcloud;本任务只对 qcloud 启用直传)。
- composer 已有 `qcloud/cos-sdk-v5 ^2.5`,但 STS 临时凭证需要再装 `qcloud/cos-sts-sdk`
- 未提交代码:`CallRecordPanel.vue` 已加「上传视频」按钮,调用 `createManualCallRecord` + `attachLocalCallRecording`,依赖上传组件吐出的 `response.data.uri/url`。AuthMiddleware 已为 `tcm.diagnosis/uploadcallrecording` 等加白名单 — 直传新接口与之解耦,不需要白名单。
## Assumptions (temporary)
- 生产环境默认存储引擎已经是 `qcloud`;切到 `local/aliyun/qiniu` 时 direct 模式需要降级回老链路。
- COS Bucket 已开启跨域 CORS(直传必须)。如未开启,需要运维在 COS 控制台配置 `AllowedOrigin = 后台域名 + 本地 dev``AllowedMethod = PUT/POST/HEAD``AllowedHeader = *``ExposeHeader: ETag`
- 后台域名固定(暂无多租户独立域名)。
- 视频文件大小有合理上限(建议 2GB)。
## Decisions (locked)
- 视频上限 **2 GB**(写进 STS policy `numeric_less_than_equal cos:content-length` + 前端 `file.size` 预检)。
- 保留 `/upload/video` 老接口;`upload/index.vue``direct` prop 默认 `false`,仅 `CallRecordPanel.vue` 这类显式声明的视频场景启用直传。
- STS 凭证有效期 **30 分钟**1800 秒)。
- 直传完成后**必须等 `oss-confirm`** 返回(写完 `file` 表 + HEAD 校验通过)才 emit `success`,保证业务侧拿到的 `uri/url` 一定有 `file_id`
## Requirements (evolving)
- [R1] 后端新增 `POST /adminapi/upload/oss-credentials`,仅当 `storage.default == qcloud` 时返回 `{provider, bucket, region, credentials, expiredTime, key, host, cdn_url_base}`;其它 driver 返回 `{provider: '<engine>', fallback: true}` 让前端自动降级到老链路。
- [R2] 后端新增 `POST /adminapi/upload/oss-confirm`HEAD 一下 `key` 校验确实存在 + size 在阈值内,写入 `file` 表(沿用 `FileService::getFileUrl`),返回与 `UploadService::video()` 完全对齐的 `{id, cid, type, name, uri, url}`
- [R3] 新装 composer 依赖 `qcloud/cos-sts-sdk`;在 `engine/Qcloud.php` 增加 `getStsCredentials($keyPrefix, $maxSize)` 方法,复用现有 `access_key/secret_key/bucket/region`
- [R4] STS policy 限定 action`name/cos:PostObject``name/cos:PutObject``name/cos:InitiateMultipartUpload``name/cos:UploadPart``name/cos:CompleteMultipartUpload``name/cos:AbortMultipartUpload``name/cos:ListParts`resource 限定 `qcs::cos:${region}:uid/${appId}:${bucket}/uploads/video/{YYYYMMDD}/*`
- [R5] 前端新增 npm 依赖 `cos-js-sdk-v5``upload/index.vue` 增加 `direct: boolean` prop(默认 `false`),`type==='video' && direct===true` 时走 STS + `cos.uploadFile`(含 `SliceSize=5MB``onProgress`),仍发出原 `success` 事件 `{ code, data: { uri, url, ... } }` 保持调用侧兼容。
- [R6] `CallRecordPanel.vue` 切到 `<upload type="video" direct :show-progress="true">`,并支持上传过程中显示百分比(沿用组件内置 `el-dialog` 进度)。
- [R7] 失败/降级路径:STS 接口失败 / response 标记 `fallback=true` / `cos-js-sdk-v5` 上传报错时,自动降级到老的 `POST /upload/video`,提示「直传失败,已切换到普通上传」。
- [R8] 安全:保留登录鉴权;STS 凭证 30 分钟有效期;前端拒绝不在 `accept` 列表的扩展名(沿用现有 `.wmv,.avi,...,.mp4,.mkv`)。
## Acceptance Criteria (evolving)
- [ ] 在 storage=qcloud 环境,从 `CallRecordPanel.vue` 上传 1.5GB mp4 文件,浏览器 Network 面板能看到流量直接打到 `*.cos.*.myqcloud.com``/upload/video` 没有调用。
- [ ] 上传过程中能看到分片进度条(5MB 一片,并发 ≥3)。
- [ ] 上传完成后,`call_record` 表正确写入 `recording_urls`,前端表格 `recording_urls_list` 渲染出可播放视频。
- [ ] `file` 表新增一行,`uri` 形如 `uploads/video/20260508/xxxxx.mp4``url` 拼接了 CDN/COS 域名。
- [ ]`dev_config.storage.default` 改成 `local`,再次上传,自动降级到老 `/upload/video` 路径,整体功能正常。
- [ ] 凭证接口未登录调用返回 401。
- [ ] 上传一个被 STS policy resource 拒绝的 key(人为篡改 prefix),COS 返回 403,前端给出明确错误。
## Definition of Done
- 单元/集成测试:暂无 PHPUnit 工程,跳过自动化测试;保证手测覆盖 AC 全部场景。
- 前端 `npm run type-check` 通过、`npm run lint` 无新增告警。
- 后端 `php -l` 通过,新接口在 admin 路由可访问。
- 文档:在 `.trellis/spec/`(如已有上传相关 spec)补充直传规范;PRD 同步关闭 Open Questions。
- COS CORS / STS 子账号最小权限策略整理为 ops 备忘(写到 prd.md 的 Technical Notes)。
## Out of Scope (explicit)
- 不做断点续传(刷新页面不保留 UploadId)。
- 不做 PostObject 回调链路。
- 不做 aliyun / qiniu 直传。
- 不改 `upload/image``upload/file` 链路(图片小文件用老链路即可)。
- 不做客户端 H5/uniapp 端直传(uniapp 本来就不走 admin)。
- 不做并发上传限流 / 全局上传队列。
## Technical Notes
- 关键文件:`server/app/common/service/storage/engine/Qcloud.php``server/app/common/service/UploadService.php``server/app/adminapi/controller/UploadController.php``admin/src/components/upload/index.vue``admin/src/views/tcm/diagnosis/components/CallRecordPanel.vue`
- composer 新增:`qcloud/cos-sts-sdk: ^3.0`
- npm 新增:`cos-js-sdk-v5`
- COS CORS 配置(运维):`AllowedOrigin: <admin 域名>``AllowedMethod: PUT,POST,HEAD,GET``AllowedHeader: *``ExposeHeader: ETag,x-cos-request-id,x-cos-version-id`
- STS 子账号最小权限:仅赋予对 `${bucket}/uploads/video/*``cos:Get*``cos:Put*``cos:Post*`、Multipart 系列动作;STS policy 在此基础上再用 condition + resource 收敛。
- Key 命名:`uploads/video/${YYYYMMDD}/${ulid}.${ext}`,与现有 `UploadService::getUploadUrl()``Ymd` 分日目录保持一致。