# 工作室画廊 COS 接入配置说明 本文档对应当前仓库当前实现。 现在已经不再使用 STS `AssumeRole`。 当前方案改为: - 服务端使用长期密钥直接签发 COS POST Policy - 管理中心小程序拿到表单签名后直传 COS - 工作室配置中的 `logo`、`bannerUrl`、`photos` 保存最终可访问 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_ID`、`COS_SECRET_KEY` 直接生成一组 POST Policy 表单字段。 4. 服务端把 `uploadUrl`、`key`、`formData`、`fileUrl`、`expiresAt` 返回给小程序。 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` - 图片访问域名 建议约定: - Bucket:`plates-1251306435` - Region:`ap-guangzhou` - Prefix:`mp/studio` ## 四、COS 控制台配置 ### 1. 创建或确认 Bucket 控制台路径:`对象存储 COS` 建议: - 地域选 `广州` 或你当前实际地域 - 存储类型标准存储即可 - Bucket 名称和环境变量保持完全一致 ### 2. 图片访问方式 当前实现保存的是直接图片 URL。 所以图片必须能被小程序和前台直接访问。 你有两种方式: 1. 直接使用 COS 源站并允许读 2. 配 CDN / 自定义域名并让这个域名可直接访问图片 如果你什么都不配,上传成功后图片可能打不开。 最直接做法: - 让这个图片 Bucket 对外可读 更稳妥做法: - 单独图片 Bucket - 用 CDN 域名做 `COS_PUBLIC_BASE_URL` ### 3. 微信小程序合法域名 微信公众平台需要补白名单: - `request 合法域名`:你的后端 API 域名 - `uploadFile 合法域名`:`https://.cos..myqcloud.com` - `downloadFile 合法域名`:图片访问域名 如果图片访问也走 COS 源站,那么 `downloadFile 合法域名` 同样加: - `https://.cos..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` - AppId:`1251306435` - Bucket:`plates-1251306435` - Prefix:`mp/studio` ```json { "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` 或线上环境: ```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_REGION`:Bucket 地域 - `COS_PUBLIC_BASE_URL`:最终展示图片的访问域名 - `COS_UPLOAD_PREFIX`:统一对象前缀 - `COS_UPLOAD_DURATION_SECONDS`:Policy 有效期秒数 现在可以删除或忽略这些旧配置: - `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_ID`、`COS_SECRET_KEY`、`COS_BUCKET`、`COS_REGION`、`COS_PUBLIC_BASE_URL`、`COS_UPLOAD_PREFIX` 配到服务端。 6. 重启服务端。 7. 在管理中心上传一张图片测试。 ## 八、接口返回内容说明 请求: ```http POST /api/admin/studio/upload-credentials Content-Type: application/json Authorization: Bearer { "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 合法域名` 是否正确 ## 十、当前实现的边界 当前仓库实现边界如下: - 只支持 `jpg`、`jpeg`、`png`、`webp`、`heic`、`heif` - 单次上传大小上限 `10MB` - 只支持普通表单直传,不支持分片上传 - 删除工作室图片时,只会从数据库配置里移除 URL,不会删除 COS 历史对象 最后一条是故意保守设计。 原因很简单: - 先保证配置删除安全 - 避免误删真实文件 如果以后要做“删配置时同步删对象”,那时再单独加 `DeleteObject` 权限。 ## 十一、初始化工作室画廊 如果你要把现在手工写死的图片 URL 一次性写入数据库,执行: ```bash 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`。 这样后面扩展、迁移、审计都会更稳。