Files
mp-pilates/docs/STUDIO_COS_SETUP.md
2026-04-15 13:58:51 +08:00

8.5 KiB
Raw Permalink Blame History

工作室画廊 COS 接入配置说明

本文档对应当前仓库当前实现。

现在已经不再使用 STS AssumeRole。 当前方案改为:

  • 服务端使用长期密钥直接签发 COS POST Policy
  • 管理中心小程序拿到表单签名后直传 COS
  • 工作室配置中的 logobannerUrlphotos 保存最终可访问 URL

当前实现代码入口:

  • packages/server/src/studio/studio-upload.service.ts
  • packages/server/src/studio/studio.controller.ts
  • packages/app/src/utils/studio-upload.ts
  • packages/app/src/pages/admin/studio.vue

一、整体链路

  1. 管理中心点击上传图片。
  2. 小程序请求服务端 POST /api/admin/studio/upload-credentials
  3. 服务端用 COS_SECRET_IDCOS_SECRET_KEY 直接生成一组 POST Policy 表单字段。
  4. 服务端把 uploadUrlkeyformDatafileUrlexpiresAt 返回给小程序。
  5. 小程序使用 uni.uploadFile 直接上传到 COS。
  6. 上传成功后,把 URL 保存到工作室配置,再调用 PUT /api/admin/studio/info 落库。

这个方案没有临时密钥,也没有角色扮演。 安全边界来自两层:

  • 服务端只为单个对象 key 签发一次表单策略
  • 表单策略有明确过期时间,过期后自动失效

二、这个方案的本质

你现在选的是“服务端代签名”的直传方案。 它和 STS 的差别是:

  • STS给前端一段时间内可用的短期密钥
  • 当前方案:不给前端密钥,只给前端一个短时有效的上传表单签名

所以结论很直接:

  • 仍然有有效期
  • 但有效期作用在 POST Policy 上,不是作用在临时密钥上

当前代码里默认有效期是 1800 秒。 环境变量:

  • COS_UPLOAD_DURATION_SECONDS

当前实现限制范围:

  • 最短 300
  • 最长 7200

三、你现在真正需要准备的东西

先确认下面几个信息:

  • COS Bucket 名称,例如 plates-1251306435
  • COS 所在地域,例如 ap-guangzhou
  • 服务端使用的 COS 长期密钥 SecretId / SecretKey
  • 图片上传前缀,例如 mp/studio
  • 图片访问域名

建议约定:

  • Bucketplates-1251306435
  • Regionap-guangzhou
  • Prefixmp/studio

四、COS 控制台配置

1. 创建或确认 Bucket

控制台路径:对象存储 COS

建议:

  • 地域选 广州 或你当前实际地域
  • 存储类型标准存储即可
  • Bucket 名称和环境变量保持完全一致

2. 图片访问方式

当前实现保存的是直接图片 URL。 所以图片必须能被小程序和前台直接访问。

你有两种方式:

  1. 直接使用 COS 源站并允许读
  2. 配 CDN / 自定义域名并让这个域名可直接访问图片

如果你什么都不配,上传成功后图片可能打不开。

最直接做法:

  • 让这个图片 Bucket 对外可读

更稳妥做法:

  • 单独图片 Bucket
  • 用 CDN 域名做 COS_PUBLIC_BASE_URL

3. 微信小程序合法域名

微信公众平台需要补白名单:

  • request 合法域名:你的后端 API 域名
  • uploadFile 合法域名https://<bucket>.cos.<region>.myqcloud.com
  • downloadFile 合法域名:图片访问域名

如果图片访问也走 COS 源站,那么 downloadFile 合法域名 同样加:

  • https://<bucket>.cos.<region>.myqcloud.com

例如:

  • https://focus.richarjiang.com
  • https://plates-1251306435.cos.ap-guangzhou.myqcloud.com

五、服务端账号需要什么权限

现在已经不需要:

  • STS
  • CAM 角色
  • AssumeRole
  • 角色信任策略
  • COS_UPLOAD_ROLE_ARN

现在服务端只需要一对可以给目标 Bucket 生成上传签名的长期密钥。

最简单的做法是:

  • 用你的主账号密钥

但生产上更合理的是:

  • 建一个专用 CAM 用户,只给这个 Bucket 上传相关权限

推荐 CAM 用户权限策略

如果你要建专用 CAM 用户,给它绑定下面这类策略即可。

把下面真实值替换成你的实际资源:

  • 地域:ap-guangzhou
  • AppId1251306435
  • Bucketplates-1251306435
  • Prefixmp/studio
{
  "version": "2.0",
  "statement": [
    {
      "effect": "allow",
      "action": [
        "name/cos:PutObject",
        "name/cos:PostObject"
      ],
      "resource": [
        "qcs::cos:ap-guangzhou:uid/1251306435:plates-1251306435/mp/studio/*"
      ]
    }
  ]
}

如果你后续还要服务端删除对象,再补:

  • name/cos:DeleteObject

当前仓库实现不需要删除对象,所以先不要额外放大权限。

六、服务端环境变量

把下面变量配置到 packages/server/.env 或线上环境:

COS_SECRET_ID=your-cos-secret-id
COS_SECRET_KEY=your-cos-secret-key
COS_BUCKET=plates-1251306435
COS_REGION=ap-guangzhou
COS_PUBLIC_BASE_URL=https://plates-1251306435.cos.ap-guangzhou.myqcloud.com
COS_UPLOAD_PREFIX=mp/studio
COS_UPLOAD_DURATION_SECONDS=1800

各字段含义:

  • COS_SECRET_ID:用于签发 POST Policy 的长期密钥 ID
  • COS_SECRET_KEY:用于签发 POST Policy 的长期密钥 Key
  • COS_BUCKET:上传目标 Bucket
  • COS_REGIONBucket 地域
  • COS_PUBLIC_BASE_URL:最终展示图片的访问域名
  • COS_UPLOAD_PREFIX:统一对象前缀
  • COS_UPLOAD_DURATION_SECONDSPolicy 有效期秒数

现在可以删除或忽略这些旧配置:

  • COS_UPLOAD_ROLE_ARN
  • COS_APP_ID
  • COS_UPLOAD_ROLE_SESSION_NAME

它们对当前实现已经没用。

七、控制台操作清单

按这个顺序做:

  1. 确认 COS Bucket 已存在。
  2. 确认图片访问域名对外可读。
  3. 在微信公众平台加好 request / uploadFile / downloadFile 合法域名。
  4. 准备一对 COS 长期密钥。
  5. COS_SECRET_IDCOS_SECRET_KEYCOS_BUCKETCOS_REGIONCOS_PUBLIC_BASE_URLCOS_UPLOAD_PREFIX 配到服务端。
  6. 重启服务端。
  7. 在管理中心上传一张图片测试。

八、接口返回内容说明

请求:

POST /api/admin/studio/upload-credentials
Content-Type: application/json
Authorization: Bearer <admin-token>

{
  "fileName": "demo.jpg",
  "contentType": "image/jpeg",
  "assetType": "gallery"
}

正常返回会包含:

  • uploadUrl
  • fileUrl
  • key
  • assetType
  • expiresAt
  • formData

formData 里会有这些字段:

  • key
  • policy
  • success_action_status
  • Content-Type
  • q-sign-algorithm
  • q-ak
  • q-key-time
  • q-sign-time
  • q-signature

这就是小程序直传需要的全部内容。

九、怎么验证是否配置正确

1. 接口层验证

调用 POST /api/admin/studio/upload-credentials

如果成功,说明:

  • 服务端长期密钥有效
  • 服务端已经能正确签发 policy

2. 上传层验证

在管理中心上传一张图,检查:

  1. COS Bucket 下是否出现对象
  2. 返回的 fileUrl 浏览器是否能直接访问
  3. 保存工作室设置后首页是否显示该图

3. 失败时怎么定位

如果 upload-credentials 接口失败,优先检查:

  • COS_SECRET_ID / COS_SECRET_KEY 是否正确
  • COS_BUCKET / COS_REGION 是否正确
  • 服务端是否已经加载最新环境变量

如果接口成功但上传失败,优先检查:

  • 小程序 uploadFile 合法域名 是否正确
  • Bucket 权限策略是否允许当前长期密钥上传到该前缀
  • Content-Type 是否被策略条件限制住

如果上传成功但图片打不开,优先检查:

  • Bucket 或图片域名是否可公网访问
  • COS_PUBLIC_BASE_URL 是否正确
  • 小程序 downloadFile 合法域名 是否正确

十、当前实现的边界

当前仓库实现边界如下:

  • 只支持 jpgjpegpngwebpheicheif
  • 单次上传大小上限 10MB
  • 只支持普通表单直传,不支持分片上传
  • 删除工作室图片时,只会从数据库配置里移除 URL不会删除 COS 历史对象

最后一条是故意保守设计。 原因很简单:

  • 先保证配置删除安全
  • 避免误删真实文件

如果以后要做“删配置时同步删对象”,那时再单独加 DeleteObject 权限。

十一、初始化工作室画廊

如果你要把现在手工写死的图片 URL 一次性写入数据库,执行:

pnpm --filter @mp-pilates/server studio:seed-gallery

脚本文件:

  • packages/server/prisma/update-studio-gallery.ts

十二、建议的生产做法

如果你后面要长期维护,建议:

  1. 图片单独放一个 Bucket。
  2. 长期密钥不要直接用主账号,换成专用 CAM 用户。
  3. 对专用 CAM 用户只给 mp/studio/* 前缀上传权限。
  4. 用 CDN 域名作为 COS_PUBLIC_BASE_URL

这样后面扩展、迁移、审计都会更稳。