Files

6.9 KiB
Raw Permalink Blame History

前端直传腾讯云 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.vuePOST /upload/videoUploadController::video)→ UploadService::video()storage/Driverengine/Qcloud::upload()putObject
  • 服务端入参依赖 $_FILES,受 PHP 上传上限约束;返回 {id, cid, type, name, uri, url}uriFileService::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 = 后台域名 + 本地 devAllowedMethod = PUT/POST/HEADAllowedHeader = *ExposeHeader: ETag
  • 后台域名固定(暂无多租户独立域名)。
  • 视频文件大小有合理上限(建议 2GB)。

Decisions (locked)

  • 视频上限 2 GB(写进 STS policy numeric_less_than_equal cos:content-length + 前端 file.size 预检)。
  • 保留 /upload/video 老接口;upload/index.vuedirect 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-confirmHEAD 一下 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 限定 actionname/cos:PostObjectname/cos:PutObjectname/cos:InitiateMultipartUploadname/cos:UploadPartname/cos:CompleteMultipartUploadname/cos:AbortMultipartUploadname/cos:ListPartsresource 限定 qcs::cos:${region}:uid/${appId}:${bucket}/uploads/video/{YYYYMMDD}/*
  • [R5] 前端新增 npm 依赖 cos-js-sdk-v5upload/index.vue 增加 direct: boolean prop(默认 false),type==='video' && direct===true 时走 STS + cos.uploadFile(含 SliceSize=5MBonProgress),仍发出原 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.mp4url 拼接了 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/imageupload/file 链路(图片小文件用老链路即可)。
  • 不做客户端 H5/uniapp 端直传(uniapp 本来就不走 admin)。
  • 不做并发上传限流 / 全局上传队列。

Technical Notes

  • 关键文件:server/app/common/service/storage/engine/Qcloud.phpserver/app/common/service/UploadService.phpserver/app/adminapi/controller/UploadController.phpadmin/src/components/upload/index.vueadmin/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,GETAllowedHeader: *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 分日目录保持一致。