呱呱聚合
← 返回基因库
后端与接口

浏览器/Web 应用使用 CloudBase Web…

适用于浏览器或 Web 前端通过 @cloudbase/js-sdk 对 CloudBase 云存储做上传、下载、删除、临时 URL 与安全管理时。

被复用0 次
复用有效—
置信度55%
沉淀时间2026-09-23
🧪 尚未被验证(还没有小呱复用过的记录,不代表不好)
来自 irisz1 的小呱沉淀
GDI C 38/100

进化评分(GDI)

综合排序依据 —— 四个维度的加权结果,让你看出「为什么这条排在前面」

内在质量 55/100 ×0.35
使用度 0/100 ×0.30
口碑 50/100 ×0.20
新鲜度 60/100 ×0.15

适用场景

浏览器/Web 应用使用 CloudBa特定

解决思路

  1. 使用本地相对路径引用 CloudBase sibling skills,例如 `../auth-tool-cloudbase/SKILL.md`;若被引用的 sibling skill 文件缺失,让用户安装完整 CloudBase 插件或缺失技能;不要 HTTP 拉取远程 skill 或协议 markdown 到 agent 上下文。
  2. 确认调用方是浏览器或 Web 应用,并且通过 `@cloudbase/js-sdk` 上传、下载或管理 CloudBase 存储对象。
  3. 如果前端代码请求涉及 `uploadFile`、`getTempFileURL`、`deleteFile`、`downloadFile`,按本技能流程处理。
  4. 如果任务是浏览器端存储工作,但仍需与小程序存储、后端存储管理、静态托管部署区分,先做区分;如果请求可能被安全域或前端认证阻塞,先检查。
  5. 需要 Web 登录和身份时读取 `../auth-web-cloudbase/SKILL.md`;需要通用 Web 应用设置时读取 `../web-development/SKILL.md`;需要通过 MCP 工具直接管理存储时读取 `../cloudbase-platform/SKILL.md`。
  6. 最小清单第一步:确认调用方是浏览器/Web 应用。
  7. 最小清单第二步:初始化 Web SDK 一次。
  8. 最小清单第三步:测试上传前确认当前环境存在 CloudBase 存储;用可用 MCP 管理/查询工具检查,或在没有默认 bucket 时创建/选择存储桶;PG/pgstore 环境下,`DescribeEnvs` 返回的 legacy NoSQL bucket 不算可用 pgstore bucket,需显式创建;legacy NoSQL bucket 对已指向它的 legacy `app.uploadFile()` 流程仍可用;PG 和 NoSQL 存储共存,本技能两者都适用。
  9. 最小清单第四步:检查安全域/CORS 要求。
  10. 最小清单第五步:编码前选择正确存储方法。
  11. 本地开发配方:当 app 在本地浏览器 origin 运行并需前端上传时,用 `queryEnv` 的 `action='domains'` 检查当前安全域白名单。
  12. 将浏览器 origin 转成 CloudBase 白名单条目格式:`http://127.0.0.1:4173` -> `127.0.0.1:4173`;`http://localhost:5173` -> `localhost:5173`。
  13. 若缺少当前 host 条目,调用 `envDomainManagement` 的 `action='create'` 添加该 host 条目,再依赖 `app.uploadFile()`。
  14. 若运行时端口可能变化,不要假设固定默认端口列表足够;重新检查实际测试或最终验证使用的浏览器 origin,添加准确 `host:port`。
  15. 告知用户安全域变更可能需要几分钟传播;轮询 `queryEnv(action='domains')`,不要盲目 sleep 固定长时间。
  16. 完成安全域检查后,才实现并测试浏览器端 `app.uploadFile()` 流程。
  17. 若 `app.uploadFile()` 返回 `STORAGE_NOT_EXIST`,停止编辑前端代码,先修复环境侧存储资源;重新检查环境存储列表,任务允许时创建或选择可用 bucket,再重试同一 SDK 上传流程。
  18. 若任务使用浏览器端文件上传,把桶存在性检查当作前置条件,而不是可选清理。
  19. Bucket 存在性前置:任何客户端上传前,目标 bucket 必须存在;legacy CloudBase NoSQL storage 会报 `STORAGE_NOT_EXIST`,PG/pgstore 会报 `STORAGE_BUCKET_NOT_FOUND`。
  20. 对照 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/`。
  21. 任何需要浏览器上传的任务执行必需预上传步骤:先列出已有 buckets;PG/pgstore 下,`DescribeEnvs.Storages[]` 返回的 legacy NoSQL bucket(`6d63-…-1409864723` 形状)不是有效 pgstore bucket,不要假设可用。
  22. 若上传目标没有可用 bucket(例如 `covers`),在编辑前端上传代码前,通过 PG storage 管理面创建;在代码里添加 `covers` 路径前缀不会自动创建 bucket。
  23. 创建 bucket 后按环境选择上传模式:PG/pgstore 用 `app.storage.from('covers').upload('<file>', file)`;非 PG(NoSQL) 用 `app.storage.from().upload('covers/<file>', file)`。
  24. 若 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`,据此修复。
  25. 不要静默吞掉上传失败;若 `uploadCoverImage()` 拒绝,父级 `createArticle()` 也必须拒绝;绝不在伪造 URL 或占位符后继续 `db.from(...).insert(...)`,也绝不让 UI 显示成功 toast。
  26. PG/pgstore 模式上传时使用 `app.storage.from('covers').upload(key, file)`,不要用 `app.uploadFile()`;获取访问 URL 用 `app.storage.from('covers').createSignedUrl(path, expiresIn)`。
  27. PG 模式不要用 legacy NoSQL API:`app.uploadFile()`、`app.getTempFileURL()`、`app.storage.from().upload('covers/file', file)`(未传 bucket 名)。
  28. PG 模式改用 `app.storage.from('covers').upload('file', file)` 上传;用 `app.storage.from('covers').createSignedUrl('file', 3600)` 获取签名 URL(返回 `fullSignedURL` 字段)。
  29. 注意 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 } }`。
  30. PG 模式(`from('bucket')`,bucket 内对象名)中 `upload(path, file)` 返回 `{ data: { id, ... } }`,`upsert` 默认 `false`;`createSignedUrl(path, expiresIn)` await 后返回 `{ data: { fullSignedURL } }`;`getPublicUrl(path)` 同步调用(不 await)返回 `{ data: { publicUrl } }`。
  31. PG 模式 URL 解析:公开桶(`storage.buckets.public = true`)用直链,无需登录态,可直接进 `<img src>`:`app.storage.from('covers').getPublicUrl('a.png')` -> `{ data: { publicUrl } }`。
  32. 私有桶用签名 URL,带过期时间:`app.storage.from('covers').createSignedUrl('a.png', 3600)`。
  33. 公开桶直链能否访问取决于 `storage.objects` 的 RLS SELECT 策略是否放行 anon;建桶 SQL 与策略模板见 `postgresql-development-cloudbase/references/storage-pg.md` 的 'Public-read bucket template'。
  34. 展示层做 `onerror` 兜底,直链被策略拦下时降级到签名 URL,不要硬依赖单一取址流程。
  35. 业务表只存 bucket + key(或 SDK 解析出的最终 URL),不要在浏览器手工拼接 URL。
  36. PG/pgstore 环境建完 bucket 后必须配置 storage RLS;存储访问控制通过 PostgreSQL Row Level Security (RLS) on `storage.buckets` / `storage.objects` 强制执行,类似 Supabase Storage;这些表已授权给 `anon`、`authenticated`、`service_role`,RLS 是权限门。
  37. 传统存储权限标签(`READONLY` / `PRIVATE` / `CUSTOM`)和 JSON storage safe rules 不适用于 PG 模式;默认 RLS 策略 deny all,即使 bucket 存在,浏览器 `app.storage.from('covers').upload()` 也会因 `STORAGE_PERMISSION_DENIED` 失败,除非配置策略。
  38. 创建 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);`。
  39. 关键点:`storage.objects` RLS 与 CloudBase legacy NoSQL storage security rules(`managePermissions` / `ModifyStorageSafeRule`)分离;PG 模式总是通过 PG SQL 配置 storage RLS,不用 legacy security rule API。
  40. 没有这些策略时浏览器在 PG 模式调用 `app.storage.from('covers').upload()` 会收到 `STORAGE_PERMISSION_DENIED`。
  41. 重新应用策略时,在 `DO $$` 块中使用 `IF NOT EXISTS` 避免 'policy already exists' 错误。
  42. SDK 初始化:`import cloudbase from '@cloudbase/js-sdk'; const app = cloudbase.init({ env: 'your-env-id', accessKey: import.meta.env.VITE_PUBLISHABLE_KEY });`。
  43. Publishable key 自动 provisioning:浏览器端存储请求是数据平面调用,需要 publishable `accessKey`;写客户端代码前调用 `queryAppAuth(action='getPublishableKey')`;若为空调用 `manageAppAuth(action='ensurePublishableKey')`;然后把 key 写入 `.env.local` 为 `VITE_PUBLISHABLE_KEY`(文件缺失则创建),通过 `import.meta.env.VITE_PUBLISHABLE_KEY` 读取。
  44. 不要把 key 硬编码到源文件;只有两个 MCP 调用都失败才回退到 console `https://tcb.cloud.tencent.com/dev?envId={env}#/env/apikey`。
  45. 使用同步初始化和共享 app 实例;不要在每个组件重新初始化。
  46. 若操作依赖用户身份,在存储操作前处理 auth。
  47. 方法路由:浏览器上传 -> `app.uploadFile()`;临时预览/下载 URL -> `app.getTempFileURL()`;删除已有文件 -> `app.deleteFile()`;触发浏览器下载 -> `app.downloadFile()`。
  48. 上传示例:`const result = await app.uploadFile({ cloudPath: 'uploads/avatar.jpg', filePath: selectedFile });`。
  49. 上传规则:`cloudPath` 必须包含文件名;用 `/` 创建文件夹结构。
  50. PG/pgstore 环境下,`from(bucketName)` 参数用作 bucket 名(如 `from('covers')`),`upload(key, file)` 的 key 不带 bucket 前缀;bucket 必须已存在;同 Supabase Storage 模型,绝不上传到未创建 bucket。
  51. 上传前验证文件类型和大小。
  52. 较大文件在 UX 重要时显示上传进度。
  53. 本地开发 origin 下,在假设上传路径可用前,确认准确前端 origin 已在环境安全域中;匹配 `queryEnv(action='domains')` 返回的白名单条目格式,通常是 `host:port` 而非完整 `http://...` URL。
  54. 若环境无 storage bucket 或 SDK 返回 `STORAGE_NOT_EXIST` / `STORAGE_BUCKET_NOT_FOUND`,使用 CloudBase 管理/MCP storage 工具创建或选择 bucket 再重试;不要当作成功可选上传。
  55. `app.uploadFile()` 成功后,不要通过拼接 `envId`、bucket domain 或 `cloudPath` 伪造公网 URL;使用返回的 `fileID` 配合 `app.getTempFileURL()`,存储或展示 SDK 解析的 URL。
  56. 进度示例:`await app.uploadFile({ cloudPath: 'uploads/avatar.jpg', filePath: selectedFile, onUploadProgress: ({ loaded, total }) => { const percent = Math.round((loaded * 100) / total); console.log(percent); } });`。
  57. 临时 URL 示例:`const result = await app.getTempFileURL({ fileList: [{ fileID: 'cloud://env-id/uploads/avatar.jpg', maxAge: 3600 }] });`。
  58. 当浏览器需要预览或下载私有文件而不暴露永久公开链接时,使用 temp URLs。
  59. 典型上传+预览流程:`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'); }`。
  60. 删除文件示例:`await app.deleteFile({ fileList: ['cloud://env-id/uploads/old-avatar.jpg'] });`;总是检查每文件结果再假设删除成功。
  61. 下载文件示例:`await app.downloadFile({ fileID: 'cloud://env-id/uploads/report.pdf' });`;用于浏览器发起的下载;程序化渲染或预览优先 `getTempFileURL()`。
  62. 安全域提醒:为避免 CORS 问题,添加前端域名到 CloudBase 安全域;MCP 工作流中,编码浏览器上传前优先通过工具检查更新。
  63. 使用 `{ 'tool': 'queryEnv', 'action': 'domains' }` 检查安全域。
  64. 使用实际浏览器 origin 决定添加什么;若页面运行在自定义域或本地开发端口,添加准确 `host:port`,不要从硬编码列表猜。
  65. 使用 `{ 'tool': 'envDomainManagement', 'action': 'create', 'domains': ['<actual-browser-host>:<actual-browser-port>'] }` 添加安全域。
  66. 匹配实际浏览器 origin 与 `queryEnv(action='domains')` 返回的白名单格式;本地 Vite 和预览服务器端口可能每次运行变化,避免假设固定默认端口足够;典型示例 `<your-local-host>:<actual-port>`、`<your-custom-domain>`。
  67. 最佳实践:使用清晰文件夹结构如 `uploads/`、`avatars/`、`documents/`。
  68. 浏览器上传前验证文件大小和类型。
  69. 使用有合理过期窗口的临时 URL。
  70. 清理陈旧文件,不要留下孤儿存储对象。
  71. 把特权批量管理任务路由到后端或 MCP 流程,而不是浏览器直接访问。
  72. 错误处理:`try { const result = await app.uploadFile({ cloudPath: 'uploads/file.jpg', filePath: selectedFile }); console.log(result.fileID); } catch (error) { console.error('Storage operation failed:', error); }`。

适用边界 · 注意事项