后端与接口
浏览器/Web 应用使用 CloudBase Web…
适用于浏览器或 Web 前端通过 @cloudbase/js-sdk 对 CloudBase 云存储做上传、下载、删除、临时 URL 与安全管理时。
🧪 尚未被验证(还没有小呱复用过的记录,不代表不好)
GDI C 38/100
进化评分(GDI)
综合排序依据 —— 四个维度的加权结果,让你看出「为什么这条排在前面」
内在质量 55/100 ×0.35
使用度 0/100 ×0.30
口碑 50/100 ×0.20
新鲜度 60/100 ×0.15
适用场景
浏览器/Web 应用使用 CloudBa特定
解决思路
- 使用本地相对路径引用 CloudBase sibling skills,例如 `../auth-tool-cloudbase/SKILL.md`;若被引用的 sibling skill 文件缺失,让用户安装完整 CloudBase 插件或缺失技能;不要 HTTP 拉取远程 skill 或协议 markdown 到 agent 上下文。
- 确认调用方是浏览器或 Web 应用,并且通过 `@cloudbase/js-sdk` 上传、下载或管理 CloudBase 存储对象。
- 如果前端代码请求涉及 `uploadFile`、`getTempFileURL`、`deleteFile`、`downloadFile`,按本技能流程处理。
- 如果任务是浏览器端存储工作,但仍需与小程序存储、后端存储管理、静态托管部署区分,先做区分;如果请求可能被安全域或前端认证阻塞,先检查。
- 需要 Web 登录和身份时读取 `../auth-web-cloudbase/SKILL.md`;需要通用 Web 应用设置时读取 `../web-development/SKILL.md`;需要通过 MCP 工具直接管理存储时读取 `../cloudbase-platform/SKILL.md`。
- 最小清单第一步:确认调用方是浏览器/Web 应用。
- 最小清单第二步:初始化 Web SDK 一次。
- 最小清单第三步:测试上传前确认当前环境存在 CloudBase 存储;用可用 MCP 管理/查询工具检查,或在没有默认 bucket 时创建/选择存储桶;PG/pgstore 环境下,`DescribeEnvs` 返回的 legacy NoSQL bucket 不算可用 pgstore bucket,需显式创建;legacy NoSQL bucket 对已指向它的 legacy `app.uploadFile()` 流程仍可用;PG 和 NoSQL 存储共存,本技能两者都适用。
- 最小清单第四步:检查安全域/CORS 要求。
- 最小清单第五步:编码前选择正确存储方法。
- 本地开发配方:当 app 在本地浏览器 origin 运行并需前端上传时,用 `queryEnv` 的 `action='domains'` 检查当前安全域白名单。
- 将浏览器 origin 转成 CloudBase 白名单条目格式:`http://127.0.0.1:4173` -> `127.0.0.1:4173`;`http://localhost:5173` -> `localhost:5173`。
- 若缺少当前 host 条目,调用 `envDomainManagement` 的 `action='create'` 添加该 host 条目,再依赖 `app.uploadFile()`。
- 若运行时端口可能变化,不要假设固定默认端口列表足够;重新检查实际测试或最终验证使用的浏览器 origin,添加准确 `host:port`。
- 告知用户安全域变更可能需要几分钟传播;轮询 `queryEnv(action='domains')`,不要盲目 sleep 固定长时间。
- 完成安全域检查后,才实现并测试浏览器端 `app.uploadFile()` 流程。
- 若 `app.uploadFile()` 返回 `STORAGE_NOT_EXIST`,停止编辑前端代码,先修复环境侧存储资源;重新检查环境存储列表,任务允许时创建或选择可用 bucket,再重试同一 SDK 上传流程。
- 若任务使用浏览器端文件上传,把桶存在性检查当作前置条件,而不是可选清理。
- Bucket 存在性前置:任何客户端上传前,目标 bucket 必须存在;legacy CloudBase NoSQL storage 会报 `STORAGE_NOT_EXIST`,PG/pgstore 会报 `STORAGE_BUCKET_NOT_FOUND`。
- 对照 Supabase 心智模型:创建 bucket 是 admin-side;上传 Supabase 用 `supabase.storage.from('covers').upload('a.png', file)`;CloudBase PG 模式用 `app.storage.from('covers').upload('a.png', file)`;非 PG 模式用 `app.storage.from().upload('covers/a.png', file)`;bucket 缺失时浏览器看到 `STORAGE_BUCKET_NOT_FOUND`(PG)或 `STORAGE_NOT_EXIST`(NoSQL),随后 SDK 因缺失 `metadata.url` 尝试 `PUT https://undefined/`。
- 任何需要浏览器上传的任务执行必需预上传步骤:先列出已有 buckets;PG/pgstore 下,`DescribeEnvs.Storages[]` 返回的 legacy NoSQL bucket(`6d63-…-1409864723` 形状)不是有效 pgstore bucket,不要假设可用。
- 若上传目标没有可用 bucket(例如 `covers`),在编辑前端上传代码前,通过 PG storage 管理面创建;在代码里添加 `covers` 路径前缀不会自动创建 bucket。
- 创建 bucket 后按环境选择上传模式:PG/pgstore 用 `app.storage.from('covers').upload('<file>', file)`;非 PG(NoSQL) 用 `app.storage.from().upload('covers/<file>', file)`。
- 若 DevTools 看到 `net::ERR_NAME_NOT_RESOLVED` 到 `https://undefined/`,这是 SDK 对缺失 `metadata.url` 的反应,几乎总是因为 bucket 不存在或 SDK 请求被上游拒绝;先检查失败的 `POST .../v1/storages/get-objects-upload-info` 响应中的 `code` 字段,如 `STORAGE_BUCKET_NOT_FOUND`、`STORAGE_CONTENT_LENGTH_REQUIRED`、`INVALID_PARAM`,据此修复。
- 不要静默吞掉上传失败;若 `uploadCoverImage()` 拒绝,父级 `createArticle()` 也必须拒绝;绝不在伪造 URL 或占位符后继续 `db.from(...).insert(...)`,也绝不让 UI 显示成功 toast。
- PG/pgstore 模式上传时使用 `app.storage.from('covers').upload(key, file)`,不要用 `app.uploadFile()`;获取访问 URL 用 `app.storage.from('covers').createSignedUrl(path, expiresIn)`。
- PG 模式不要用 legacy NoSQL API:`app.uploadFile()`、`app.getTempFileURL()`、`app.storage.from().upload('covers/file', file)`(未传 bucket 名)。
- PG 模式改用 `app.storage.from('covers').upload('file', file)` 上传;用 `app.storage.from('covers').createSignedUrl('file', 3600)` 获取签名 URL(返回 `fullSignedURL` 字段)。
- 注意 v3 SDK 返回形状:传统模式(`from()` 无参,`cloud://` fileID)中 `upload(path, file)` 返回 `{ data: { id, path, fullPath } }`,`upsert` 默认 `true`;`createSignedUrl(path, expiresIn)` await 后返回 `{ data: { signedUrl } }`;`getPublicUrl(path)` await 后返回 `{ data: { publicUrl } }`。
- PG 模式(`from('bucket')`,bucket 内对象名)中 `upload(path, file)` 返回 `{ data: { id, ... } }`,`upsert` 默认 `false`;`createSignedUrl(path, expiresIn)` await 后返回 `{ data: { fullSignedURL } }`;`getPublicUrl(path)` 同步调用(不 await)返回 `{ data: { publicUrl } }`。
- PG 模式 URL 解析:公开桶(`storage.buckets.public = true`)用直链,无需登录态,可直接进 `<img src>`:`app.storage.from('covers').getPublicUrl('a.png')` -> `{ data: { publicUrl } }`。
- 私有桶用签名 URL,带过期时间:`app.storage.from('covers').createSignedUrl('a.png', 3600)`。
- 公开桶直链能否访问取决于 `storage.objects` 的 RLS SELECT 策略是否放行 anon;建桶 SQL 与策略模板见 `postgresql-development-cloudbase/references/storage-pg.md` 的 'Public-read bucket template'。
- 展示层做 `onerror` 兜底,直链被策略拦下时降级到签名 URL,不要硬依赖单一取址流程。
- 业务表只存 bucket + key(或 SDK 解析出的最终 URL),不要在浏览器手工拼接 URL。
- PG/pgstore 环境建完 bucket 后必须配置 storage RLS;存储访问控制通过 PostgreSQL Row Level Security (RLS) on `storage.buckets` / `storage.objects` 强制执行,类似 Supabase Storage;这些表已授权给 `anon`、`authenticated`、`service_role`,RLS 是权限门。
- 传统存储权限标签(`READONLY` / `PRIVATE` / `CUSTOM`)和 JSON storage safe rules 不适用于 PG 模式;默认 RLS 策略 deny all,即使 bucket 存在,浏览器 `app.storage.from('covers').upload()` 也会因 `STORAGE_PERMISSION_DENIED` 失败,除非配置策略。
- 创建 bucket 后用 `managePgDatabase(action='execute', confirm=true)` 执行 SQL:`ALTER TABLE storage.objects ENABLE ROW LEVEL SECURITY;`,`CREATE POLICY authenticated_upload ON storage.objects FOR INSERT TO authenticated WITH CHECK (true);`,`CREATE POLICY authenticated_read ON storage.objects FOR SELECT TO authenticated USING (true);`,可选 `CREATE POLICY users_manage_own ON storage.objects FOR UPDATE TO authenticated USING (auth.uid() = owner_id) WITH CHECK (auth.uid() = owner_id);`。
- 关键点:`storage.objects` RLS 与 CloudBase legacy NoSQL storage security rules(`managePermissions` / `ModifyStorageSafeRule`)分离;PG 模式总是通过 PG SQL 配置 storage RLS,不用 legacy security rule API。
- 没有这些策略时浏览器在 PG 模式调用 `app.storage.from('covers').upload()` 会收到 `STORAGE_PERMISSION_DENIED`。
- 重新应用策略时,在 `DO $$` 块中使用 `IF NOT EXISTS` 避免 'policy already exists' 错误。
- SDK 初始化:`import cloudbase from '@cloudbase/js-sdk'; const app = cloudbase.init({ env: 'your-env-id', accessKey: import.meta.env.VITE_PUBLISHABLE_KEY });`。
- Publishable key 自动 provisioning:浏览器端存储请求是数据平面调用,需要 publishable `accessKey`;写客户端代码前调用 `queryAppAuth(action='getPublishableKey')`;若为空调用 `manageAppAuth(action='ensurePublishableKey')`;然后把 key 写入 `.env.local` 为 `VITE_PUBLISHABLE_KEY`(文件缺失则创建),通过 `import.meta.env.VITE_PUBLISHABLE_KEY` 读取。
- 不要把 key 硬编码到源文件;只有两个 MCP 调用都失败才回退到 console `https://tcb.cloud.tencent.com/dev?envId={env}#/env/apikey`。
- 使用同步初始化和共享 app 实例;不要在每个组件重新初始化。
- 若操作依赖用户身份,在存储操作前处理 auth。
- 方法路由:浏览器上传 -> `app.uploadFile()`;临时预览/下载 URL -> `app.getTempFileURL()`;删除已有文件 -> `app.deleteFile()`;触发浏览器下载 -> `app.downloadFile()`。
- 上传示例:`const result = await app.uploadFile({ cloudPath: 'uploads/avatar.jpg', filePath: selectedFile });`。
- 上传规则:`cloudPath` 必须包含文件名;用 `/` 创建文件夹结构。
- PG/pgstore 环境下,`from(bucketName)` 参数用作 bucket 名(如 `from('covers')`),`upload(key, file)` 的 key 不带 bucket 前缀;bucket 必须已存在;同 Supabase Storage 模型,绝不上传到未创建 bucket。
- 上传前验证文件类型和大小。
- 较大文件在 UX 重要时显示上传进度。
- 本地开发 origin 下,在假设上传路径可用前,确认准确前端 origin 已在环境安全域中;匹配 `queryEnv(action='domains')` 返回的白名单条目格式,通常是 `host:port` 而非完整 `http://...` URL。
- 若环境无 storage bucket 或 SDK 返回 `STORAGE_NOT_EXIST` / `STORAGE_BUCKET_NOT_FOUND`,使用 CloudBase 管理/MCP storage 工具创建或选择 bucket 再重试;不要当作成功可选上传。
- `app.uploadFile()` 成功后,不要通过拼接 `envId`、bucket domain 或 `cloudPath` 伪造公网 URL;使用返回的 `fileID` 配合 `app.getTempFileURL()`,存储或展示 SDK 解析的 URL。
- 进度示例:`await app.uploadFile({ cloudPath: 'uploads/avatar.jpg', filePath: selectedFile, onUploadProgress: ({ loaded, total }) => { const percent = Math.round((loaded * 100) / total); console.log(percent); } });`。
- 临时 URL 示例:`const result = await app.getTempFileURL({ fileList: [{ fileID: 'cloud://env-id/uploads/avatar.jpg', maxAge: 3600 }] });`。
- 当浏览器需要预览或下载私有文件而不暴露永久公开链接时,使用 temp URLs。
- 典型上传+预览流程:`const uploadResult = await app.uploadFile({ cloudPath: 'uploads/avatar.jpg', filePath: selectedFile }); const tempUrlResult = await app.getTempFileURL({ fileList: [{ fileID: uploadResult.fileID, maxAge: 3600 }] }); const previewUrl = tempUrlResult.fileList?.[0]?.tempFileURL || tempUrlResult.fileList?.[0]?.download_url; if (!previewUrl) { throw new Error('Failed to resolve temporary file URL after upload'); }`。
- 删除文件示例:`await app.deleteFile({ fileList: ['cloud://env-id/uploads/old-avatar.jpg'] });`;总是检查每文件结果再假设删除成功。
- 下载文件示例:`await app.downloadFile({ fileID: 'cloud://env-id/uploads/report.pdf' });`;用于浏览器发起的下载;程序化渲染或预览优先 `getTempFileURL()`。
- 安全域提醒:为避免 CORS 问题,添加前端域名到 CloudBase 安全域;MCP 工作流中,编码浏览器上传前优先通过工具检查更新。
- 使用 `{ 'tool': 'queryEnv', 'action': 'domains' }` 检查安全域。
- 使用实际浏览器 origin 决定添加什么;若页面运行在自定义域或本地开发端口,添加准确 `host:port`,不要从硬编码列表猜。
- 使用 `{ 'tool': 'envDomainManagement', 'action': 'create', 'domains': ['<actual-browser-host>:<actual-browser-port>'] }` 添加安全域。
- 匹配实际浏览器 origin 与 `queryEnv(action='domains')` 返回的白名单格式;本地 Vite 和预览服务器端口可能每次运行变化,避免假设固定默认端口足够;典型示例 `<your-local-host>:<actual-port>`、`<your-custom-domain>`。
- 最佳实践:使用清晰文件夹结构如 `uploads/`、`avatars/`、`documents/`。
- 浏览器上传前验证文件大小和类型。
- 使用有合理过期窗口的临时 URL。
- 清理陈旧文件,不要留下孤儿存储对象。
- 把特权批量管理任务路由到后端或 MCP 流程,而不是浏览器直接访问。
- 错误处理:`try { const result = await app.uploadFile({ cloudPath: 'uploads/file.jpg', filePath: selectedFile }); console.log(result.fileID); } catch (error) { console.error('Storage operation failed:', error); }`。
适用边界 · 注意事项
- 不要用于小程序文件 API。
- 不要用于后端或 agent 侧通过 MCP 直接管理存储。
- 不要用于通过 manageHosting(action='upload') 部署静态网站托管,也不要用于数据库操作。