﻿---
hide_table_of_contents: true
---

import TabItem from '@theme/TabItem';
import ApiIntro from '../components/ApiIntro';
import Tabs from '@theme/Tabs';

# 传统模式云存储

CloudBase JS SDK v3 在传统模式下通过 `app.storage.from()` 提供云存储文件操作能力。传统模式面向环境内置云存储，上传时传入云存储相对路径，上传成功后返回完整 `fileID`；下载、删除、签名 URL、文件信息查询等操作建议使用完整 `fileID`。

:::info 提示
传统模式调用云存储相关 API 时，使用的是 [云存储 HTTP API](../../http-api/storage/云存储) 的开放能力。在使用前，请前往 [云开发平台/身份认证/权限控制](https://tcb.cloud.tencent.com/dev?envId=#/identity/auth-control/detail?roleIdentity=registerUser&tab=strategy) 确认 `StoragesHttpApiAllow` 的策略管理配置是否符合预期。详细说明请参考 [策略管理说明文档](./strategy)。
:::

:::tip 与 PG 模式云存储的差异
传统模式与 PG 模式 API 的关键差异：

| 维度 | 传统模式 | PG 模式 |
|------|---------|---------|
| 入口 | `app.storage.from()` | `app.storage.from("bucketId")` |
| 路径参数 | `cloud://` fileID | Bucket 内对象名，如 `user-123/avatar.png` |
| 权限模型 | 内置权限 + JSON 安全规则 | PostgreSQL RLS Policy |
| 覆盖默认值 | `upsert` 默认 `true` | `upsert` 默认 `false` |
| 图片转换 | `download()` / 签名 URL 支持 `TransformOptions` | 不支持 |
| Bucket 管理 | 不支持 | 支持 `createBucket` / `listBuckets` 等 |
| 文件列表 | 不支持 | 支持 `list()` |
| 流式下载 | 不支持 | `download().asStream()` |

详见：[API 参考（PG 模式）](../webv3-pg/storage)
:::

## 快速开始 {#quick-start}

```ts
import cloudbase from "@cloudbase/js-sdk";

const app = cloudbase.init({ env: "your-env-id" });
const storage = app.storage.from();

const { data, error } = await storage.upload("images/photo.jpg", file);
if (error) throw error;

const fileID = data.id;
const { data: blob } = await storage.download(fileID);
const { data: signed } = await storage.createSignedUrl(fileID, 3600);
await storage.remove([fileID]);
```

## API 方法 {#api-method}

### 客户端入口 {#client-entry}

- [from()](#from) - 获取传统模式云存储文件操作客户端
- [throwOnError()](#throwonerror) - 错误时抛出异常

### 文件操作 {#file-operations}

- [upload()](#upload) - 上传文件
- [update()](#update) - 更新文件
- [download()](#download) - 下载文件
- [remove()](#remove) - 删除文件
- [move()](#move) - 移动文件
- [copy()](#copy) - 复制文件

### URL 管理 {#url-management}

- [createSignedUrl()](#createsignedurl) - 创建签名 URL
- [createSignedUrls()](#createsignedurls) - 批量创建签名 URL
- [getPublicUrl()](#getpublicurl) - 获取公开 URL
- [createSignedUploadUrl()](#createsigneduploadurl) - 创建上传签名 URL

### 文件信息 {#file-info}

- [info()](#info) - 获取文件信息
- [exists()](#exists) - 检查文件是否存在


---

## from {#from}

```ts
from(): ClassicStorageFileApi
```

<ApiIntro parameter={{
input: [],
output: [{ name: "ClassicStorageFileApi", type: "Object", required: true, description: "传统模式 / 传统兼容对象操作客户端。" }]
}}>

```ts
const storage = app.storage.from();
```

</ApiIntro>


---

## throwOnError {#throwonerror}

```ts
throwOnError(): this
```

<ApiIntro parameter={{
input: [],
output: [{ name: "this", type: "ClassicStorageFileApi", required: true, description: "当前客户端实例，支持链式调用。" }]
}}>

```ts
const storage = app.storage.from().throwOnError();
await storage.upload("file.txt", file);
```

</ApiIntro>


---

## upload {#upload}

上传文件到云存储。

```ts
upload(
  path: string,
  fileBody: FileBody,
  fileOptions?: FileOptions
): Promise<
  | { data: { id: string; path: string; fullPath: string }; error: null }
  | { data: null; error: StorageError }
>

type FileBody = Blob | ArrayBuffer | ArrayBufferView | Uint8Array | string | { size?: number; byteLength?: number; [key: string]: any };

interface FileOptions {
  cacheControl?: string;
  contentType?: string;
  metadata?: Record<string, any>;
  upsert?: boolean; // 默认 true
}
```

<ApiIntro parameter={{
input: [
{ name: "path", type: "string", required: true, description: "云存储相对路径，如 `images/photo.jpg`。" },
{ name: "fileBody", type: "FileBody", required: true, description: "文件内容。" },
{ name: "fileOptions", type: "FileOptions", required: false, description: "上传选项。传统模式默认 `upsert: true`。", children: [
{ name: "cacheControl", type: "string", required: false, description: "缓存控制，如 `max-age=3600`。" },
{ name: "contentType", type: "string", required: false, description: "文件 MIME 类型，如 `image/jpeg`。" },
{ name: "metadata", type: "object", required: false, description: "自定义元数据。" },
{ name: "upsert", type: "boolean", required: false, description: "是否覆盖已存在的文件，默认 `true`。" }
] }
],
output: [
{ name: "data.id", type: "string", required: true, description: "CloudBase fileID，后续下载、删除、签名 URL 建议使用该值。" },
{ name: "data.path", type: "string", required: true, description: "上传路径。" },
{ name: "data.fullPath", type: "string", required: true, description: "文件完整路径。" },
{ name: "error", type: "StorageError | null", required: false, description: "错误信息。" }
]
}}>
<Tabs>
<TabItem value="1" label="基础上传" default>

```typescript
// 上传文件
const { data, error } = await app.storage
  .from()
  .upload("images/photo.jpg", file);

if (error) {
  console.error("上传失败:", error);
} else {
  console.log("上传成功:", data);
  console.log("文件 ID:", data.id);
  console.log("文件路径:", data.path);
}
```

</TabItem>
<TabItem value="2" label="带选项上传">

```typescript
// 上传文件并设置选项
const { data, error } = await app.storage
  .from()
  .upload("images/photo.jpg", file, {
    cacheControl: "max-age=3600",
    contentType: "image/jpeg",
    metadata: {
      author: "John Doe",
      uploadedAt: new Date().toISOString(),
    },
  });

if (error) {
  console.error("上传失败:", error);
} else {
  console.log("上传成功:", data);
}
```

</TabItem>
</Tabs>
</ApiIntro>


---

## update {#update}

更新已存在的文件。

```ts
update(path: string, fileBody: FileBody, fileOptions?: FileOptions): Promise<{ data, error }>
```

<ApiIntro parameter={{
input: [
{ name: "path", type: "string", required: true, description: "云存储相对路径。" },
{ name: "fileBody", type: "FileBody", required: true, description: "新的文件内容。" },
{ name: "fileOptions", type: "FileOptions", required: false, description: "上传选项。" }
],
output: [
{ name: "data", type: "{ id; path; fullPath } | null", required: false, description: "更新结果。" },
{ name: "error", type: "StorageError | null", required: false, description: "错误信息。" }
]
}}>
<Tabs>
<TabItem value="1" label="更新文件" default>

```typescript
// 更新文件内容
const { data, error } = await app.storage
  .from()
  .update("images/photo.jpg", newFile);

if (error) {
  console.error("更新失败:", error);
} else {
  console.log("更新成功:", data);
}
```

</TabItem>
<TabItem value="2" label="更新并修改元数据">

```typescript
// 更新文件并修改元数据
const { data, error } = await app.storage
  .from()
  .update("images/photo.jpg", newFile, {
    cacheControl: "max-age=7200",
    metadata: {
      updatedAt: new Date().toISOString(),
      version: "2.0",
    },
  });

if (error) {
  console.error("更新失败:", error);
} else {
  console.log("文件已更新:", data.path);
}
```

</TabItem>
</Tabs>
</ApiIntro>


---

## download {#download}

下载文件并返回内容。

```ts
download(
  fileId: string,
  options?: TransformOptions
): Promise<{ data: Blob; error: null } | { data: null; error: StorageError }>

interface TransformOptions {
  width?: number;
  height?: number;
  quality?: number;
  format?: "jpg" | "png" | "webp";
}
```

<ApiIntro parameter={{
input: [
{ name: "fileId", type: "string", required: true, description: "完整 CloudBase fileID。" },
{ name: "options", type: "TransformOptions", required: false, description: "图片转换选项。" }
],
output: [
{ name: "data", type: "Blob | null", required: false, description: "文件内容。" },
{ name: "error", type: "StorageError | null", required: false, description: "错误信息。" }
]
}}>
<Tabs>
<TabItem value="1" label="下载文件" default>

```typescript
// 下载原始文件
const { data, error } = await app.storage
  .from()
  .download("cloud://envId.xxx/images/photo.jpg");

if (data) {
  // 创建下载链接
  const url = URL.createObjectURL(data);
  const a = document.createElement("a");
  a.href = url;
  a.download = "photo.jpg";
  a.click();
}
```

</TabItem>
<TabItem value="2" label="下载缩略图">

```typescript
// 下载并转换图片为缩略图
const { data: thumbnail, error } = await app.storage
  .from()
  .download("cloud://envId.xxx/images/photo.jpg", {
    width: 300,
    height: 200,
    quality: 80,
  });

if (thumbnail) {
  // 显示缩略图
  const url = URL.createObjectURL(thumbnail);
  document.getElementById("thumbnail").src = url;
}
```

</TabItem>
<TabItem value="3" label="转换格式">

```typescript
// 下载并转换图片格式
const { data: webpImage, error } = await app.storage
  .from()
  .download("cloud://envId.xxx/images/photo.jpg", {
    format: "webp",
    quality: 90,
  });

if (webpImage) {
  console.log("已转换为 WebP 格式");
}
```

</TabItem>
</Tabs>
</ApiIntro>


---

## remove {#remove}

删除一个或多个文件。

```ts
remove(paths: string[]): Promise<{ data: FileObject[]; error: null } | { data: null; error: StorageError }>
```

<ApiIntro parameter={{
input: [{ name: "paths", type: "string[]", required: true, description: "完整 fileID 数组。" }],
output: [
{ name: "data", type: "FileObject[] | null", required: false, description: "删除结果。" },
{ name: "error", type: "StorageError | null", required: false, description: "错误信息。" }
]
}}>
<Tabs>
<TabItem value="1" label="删除单个文件" default>

```typescript
// 删除单个文件
const { data, error } = await app.storage
  .from()
  .remove(["cloud://envId.xxx/images/photo.jpg"]);

if (error) {
  console.error("删除失败:", error);
} else {
  console.log("删除成功:", data);
}
```

</TabItem>
<TabItem value="2" label="批量删除">

```typescript
// 批量删除多个文件
const { data, error } = await app.storage
  .from()
  .remove([
    "cloud://envId.xxx/images/photo1.jpg",
    "cloud://envId.xxx/images/photo2.jpg",
    "cloud://envId.xxx/documents/file.pdf",
  ]);

if (error) {
  console.error("删除失败:", error);
} else {
  console.log(`成功删除 ${data.length} 个文件`);
}
```

</TabItem>
</Tabs>
</ApiIntro>


---

## move {#move}

移动文件到新位置。

```ts
move(fromPath: string, toPath: string): Promise<{ data: { message: string }; error: null } | { data: null; error: StorageError }>
```

<ApiIntro parameter={{
input: [
{ name: "fromPath", type: "string", required: true, description: "源路径。" },
{ name: "toPath", type: "string", required: true, description: "目标路径。" }
],
output: [
{ name: "data.message", type: "string", required: false, description: "操作结果消息。" },
{ name: "error", type: "StorageError | null", required: false, description: "错误信息。" }
]
}}>
<Tabs>
<TabItem value="1" label="移动文件" default>

```typescript
// 移动文件到新位置
const { data, error } = await app.storage
  .from()
  .move("images/old-photo.jpg", "images/archive/photo.jpg");

if (error) {
  console.error("移动失败:", error);
} else {
  console.log("移动成功:", data.message);
}
```

</TabItem>
<TabItem value="2" label="重命名文件">

```typescript
// 重命名文件（移动到同一目录下的新名称）
const { data, error } = await app.storage
  .from()
  .move("images/photo.jpg", "images/new-photo.jpg");

if (error) {
  console.error("重命名失败:", error);
} else {
  console.log("重命名成功");
}
```

</TabItem>
</Tabs>
</ApiIntro>


---

## copy {#copy}

复制文件到新位置。

```ts
copy(fromPath: string, toPath: string): Promise<{ data: { path: string }; error: null } | { data: null; error: StorageError }>
```

<ApiIntro parameter={{
input: [
{ name: "fromPath", type: "string", required: true, description: "源路径。" },
{ name: "toPath", type: "string", required: true, description: "目标路径。" }
],
output: [
{ name: "data.path", type: "string", required: false, description: "目标路径。" },
{ name: "error", type: "StorageError | null", required: false, description: "错误信息。" }
]
}}>
<Tabs>
<TabItem value="1" label="复制文件" default>

```typescript
// 复制文件到新位置
const { data, error } = await app.storage
  .from()
  .copy("images/photo.jpg", "images/backup/photo.jpg");

if (error) {
  console.error("复制失败:", error);
} else {
  console.log("复制成功，新文件路径:", data.path);
}
```

</TabItem>
<TabItem value="2" label="创建副本">

```typescript
// 在同一目录下创建副本
const { data, error } = await app.storage
  .from()
  .copy("images/photo.jpg", "images/photo-copy.jpg");

if (error) {
  console.error("创建副本失败:", error);
} else {
  console.log("副本已创建:", data.path);
}
```

</TabItem>
</Tabs>
</ApiIntro>


---

## createSignedUrl {#createsignedurl}

创建文件临时访问链接。

```ts
createSignedUrl(
  path: string,
  expiresIn: number,
  options?: { download?: string | boolean; transform?: TransformOptions }
): Promise<{ data: { signedUrl: string }; error: null } | { data: null; error: StorageError }>
```

<ApiIntro parameter={{
input: [
{ name: "path", type: "string", required: true, description: "完整 fileID。" },
{ name: "expiresIn", type: "number", required: true, description: "有效期，单位秒。" },
{ name: "options", type: "Object", required: false, description: "下载和图片转换选项。" }
],
output: [
{ name: "data.signedUrl", type: "string", required: false, description: "签名访问链接。" },
{ name: "error", type: "StorageError | null", required: false, description: "错误信息。" }
]
}}>
<Tabs>
<TabItem value="1" label="创建临时链接" default>

```typescript
// 创建 1 小时有效的临时链接
const { data, error } = await app.storage
  .from()
  .createSignedUrl("cloud://envId.xxx/images/photo.jpg", 3600);

if (error) {
  console.error("创建失败:", error);
} else {
  console.log("临时链接:", data.signedUrl);
  // 可以直接使用这个链接访问文件
}
```

</TabItem>
<TabItem value="2" label="创建缩略图链接">

```typescript
// 创建缩略图的临时链接
const { data, error } = await app.storage
  .from()
  .createSignedUrl("cloud://envId.xxx/images/photo.jpg", 3600, {
    width: 300,
    height: 200,
    quality: 80,
  });

if (error) {
  console.error("创建失败:", error);
} else {
  // 使用缩略图链接
  document.getElementById("thumbnail").src = data.signedUrl;
}
```

</TabItem>
<TabItem value="3" label="短期分享链接">

```typescript
// 创建 5 分钟有效的短期分享链接
const { data, error } = await app.storage
  .from()
  .createSignedUrl("cloud://envId.xxx/images/photo.jpg", 300);

if (error) {
  console.error("创建失败:", error);
} else {
  // 分享这个链接，5 分钟后自动失效
  navigator.clipboard.writeText(data.signedUrl);
  alert("分享链接已复制到剪贴板");
}
```

</TabItem>
</Tabs>
</ApiIntro>


---

## createSignedUrls {#createsignedurls}

批量创建临时访问链接。

```ts
createSignedUrls(paths: string[], expiresIn: number): Promise<{ data, error }>
```

<ApiIntro parameter={{
input: [
{ name: "paths", type: "string[]", required: true, description: "完整 fileID 数组。" },
{ name: "expiresIn", type: "number", required: true, description: "有效期，单位秒。" }
],
output: [
{ name: "data", type: "Array | null", required: false, description: "签名链接结果数组。" },
{ name: "error", type: "StorageError | null", required: false, description: "错误信息。" }
]
}}>
<Tabs>
<TabItem value="1" label="批量创建链接" default>

```typescript
// 批量创建多个文件的临时链接
const { data, error } = await app.storage
  .from()
  .createSignedUrls(
    [
      "cloud://envId.xxx/images/photo1.jpg",
      "cloud://envId.xxx/images/photo2.jpg",
      "cloud://envId.xxx/images/photo3.jpg",
    ],
    3600
  );

if (error) {
  console.error("创建失败:", error);
} else {
  data.forEach((item) => {
    console.log(`${item.path}: ${item.signedUrl}`);
  });
}
```

</TabItem>
<TabItem value="2" label="批量创建缩略图链接">

```typescript
// 批量创建缩略图链接
const { data, error } = await app.storage
  .from()
  .createSignedUrls(
    [
      "cloud://envId.xxx/images/photo1.jpg",
      "cloud://envId.xxx/images/photo2.jpg",
      "cloud://envId.xxx/images/photo3.jpg",
    ],
    3600
  );

if (error) {
  console.error("创建失败:", error);
} else {
  // 显示所有缩略图
  const gallery = document.getElementById("gallery");
  data.forEach((item) => {
    const img = document.createElement("img");
    img.src = item.signedUrl;
    gallery.appendChild(img);
  });
}
```

</TabItem>
</Tabs>
</ApiIntro>


---

## getPublicUrl {#getpublicurl}

获取公开访问 URL。

```ts
getPublicUrl(path: string, options?: { download?: string | boolean; transform?: TransformOptions }): Promise<{ data: { publicUrl: string }; error: null } | { data: null; error: StorageError }>
```

<ApiIntro parameter={{
input: [{ name: "path", type: "string", required: true, description: "完整 fileID。" }],
output: [{ name: "data.publicUrl", type: "string", required: false, description: "访问 URL。" }]
}}>
<Tabs>
<TabItem value="1" label="获取公开链接" default>

```typescript
// 获取文件的公开访问链接
const { data } = await app.storage
  .from()
  .getPublicUrl("cloud://envId.xxx/images/photo.jpg");

console.log("公开链接:", data.publicUrl);
// 可以直接使用这个链接（如果文件设置为公开访问）
```

</TabItem>
<TabItem value="2" label="获取缩略图链接">

```typescript
// 获取缩略图的公开链接
const { data } = await app.storage
  .from()
  .getPublicUrl("cloud://envId.xxx/images/photo.jpg", {
    width: 300,
    height: 200,
    quality: 80,
  });

// 在 img 标签中使用
document.getElementById("thumbnail").src = data.publicUrl;
```

</TabItem>
</Tabs>
</ApiIntro>


---

## info {#info}

获取文件信息。

```ts
info(pathOrFileId: string): Promise<{ data: FileInfo; error: null } | { data: null; error: StorageError }>
```

<ApiIntro parameter={{
input: [{ name: "pathOrFileId", type: "string", required: true, description: "完整 fileID 或相对路径。" }],
output: [{ name: "data", type: "FileInfo | null", required: false, description: "文件信息。" }, { name: "error", type: "StorageError | null", required: false, description: "错误信息。" }]
}}>
<Tabs>
<TabItem value="1" label="获取文件信息" default>

```typescript
// 获取文件详细信息
const { data, error } = await app.storage
  .from()
  .info("cloud://envId.xxx/images/photo.jpg");

if (error) {
  console.error("获取失败:", error);
} else {
  console.log("文件名:", data.name);
  console.log("文件大小:", data.size, "字节");
  console.log("创建时间:", data.created_at);
  console.log("更新时间:", data.updated_at);
  console.log("元数据:", data.metadata);
}
```

</TabItem>
<TabItem value="2" label="显示文件详情">

```typescript
// 在界面上显示文件详情
const { data, error } = await app.storage
  .from()
  .info("cloud://envId.xxx/documents/report.pdf");

if (data) {
  const sizeInMB = (data.size / 1024 / 1024).toFixed(2);
  document.getElementById("fileName").textContent = data.name;
  document.getElementById("fileSize").textContent = `${sizeInMB} MB`;
  document.getElementById("createdAt").textContent = new Date(
    data.created_at
  ).toLocaleString();
}
```

</TabItem>
</Tabs>
</ApiIntro>


---

## exists {#exists}

检查文件是否存在。

```ts
exists(pathOrFileId: string): Promise<{ data: boolean; error: null } | { data: null; error: StorageError }>
```

<ApiIntro parameter={{
input: [{ name: "pathOrFileId", type: "string", required: true, description: "完整 fileID 或相对路径。" }],
output: [{ name: "data", type: "boolean | null", required: false, description: "是否存在。" }, { name: "error", type: "StorageError | null", required: false, description: "错误信息。" }]
}}>
<Tabs>
<TabItem value="1" label="检查文件存在" default>

```typescript
// 检查文件是否存在
const { data: exists, error } = await app.storage
  .from()
  .exists("cloud://envId.xxx/images/photo.jpg");

if (error) {
  console.error("检查失败:", error);
} else if (exists) {
  console.log("文件存在");
} else {
  console.log("文件不存在");
}
```

</TabItem>
<TabItem value="2" label="上传前检查">

```typescript
// 上传前检查文件是否已存在
const { data: exists } = await app.storage
  .from()
  .exists("cloud://envId.xxx/images/photo.jpg");

if (exists) {
  // 文件已存在，询问是否覆盖
  const shouldOverwrite = confirm("文件已存在，是否覆盖？");
  if (shouldOverwrite) {
    await app.storage.from().update("images/photo.jpg", file);
  }
} else {
  // 文件不存在，直接上传
  await app.storage.from().upload("images/photo.jpg", file);
}
```

</TabItem>
</Tabs>
</ApiIntro>


---

## createSignedUploadUrl {#createsigneduploadurl}

创建上传签名 URL。

```ts
createSignedUploadUrl(path: string): Promise<{ data, error }>
```

<ApiIntro parameter={{
input: [{ name: "path", type: "string", required: true, description: "上传目标路径。" }],
output: [{ name: "data", type: "Object | null", required: false, description: "签名上传 URL 与 CloudBase 上传元信息。" }, { name: "error", type: "StorageError | null", required: false, description: "错误信息。" }]
}}>
<Tabs>
<TabItem value="1" label="创建上传链接" default>

```typescript
// 创建用于上传的签名 URL
const { data, error } = await app.storage
  .from()
  .createSignedUploadUrl("cloud://envId.xxx/images/photo.jpg");

if (error) {
  console.error("创建失败:", error);
} else {
  console.log("上传 URL:", data.signedUrl);
  console.log("上传令牌:", data.token);

  // 使用这个 URL 直接上传文件
  const formData = new FormData();
  formData.append("file", file);

  await fetch(data.signedUrl, {
    method: "PUT",
    body: file,
    headers: {
      "Content-Type": file.type,
    },
  });
}
```

</TabItem>
<TabItem value="2" label="客户端直传">

```typescript
// 在客户端实现直传功能
async function uploadFileDirectly(file) {
  // 1. 获取签名上传 URL
  const { data, error } = await app.storage
    .from()
    .createSignedUploadUrl(`cloud://envId.xxx/uploads/${file.name}`);

  if (error) {
    console.error("获取上传 URL 失败:", error);
    return;
  }

  // 2. 直接上传到云存储
  const uploadResponse = await fetch(data.signedUrl, {
    method: "PUT",
    body: file,
    headers: {
      "Content-Type": file.type,
    },
  });

  if (uploadResponse.ok) {
    console.log("上传成功");
  } else {
    console.error("上传失败");
  }
}
```

</TabItem>
</Tabs>
</ApiIntro>


---

## 类型定义 {#type-definition}

```ts
type Result<T> =
  | { data: T; error: null }
  | { data: null; error: StorageError };

type FileBody = Blob | ArrayBuffer | ArrayBufferView | Uint8Array | string | { size?: number; byteLength?: number; [key: string]: any };

interface UploadResult {
  id: string;
  path: string;
  fullPath: string;
}

interface FileOptions {
  cacheControl?: string;
  contentType?: string;
  metadata?: Record<string, any>;
  upsert?: boolean;
}

interface TransformOptions {
  width?: number;
  height?: number;
  quality?: number;
  format?: "jpg" | "png" | "webp";
}
```

---

## 迁移指南 {#migration-guide}

如果你正在从 JS SDK v2 或旧版文件 API 迁移到 JS SDK v3 的传统模式云存储 API，可以按下表替换：

| 旧版 API | JS SDK v3 传统模式 API |
| ---- | ---- |
| `app.uploadFile()` | `app.storage.from().upload()` |
| `app.downloadFile()` | `app.storage.from().download()` |
| `app.getTempFileURL()` | `app.storage.from().createSignedUrl()` |
| `app.deleteFile()` | `app.storage.from().remove()` |

迁移时建议保存 `upload()` 返回的完整 `fileID`，并在下载、删除、生成签名 URL、查询信息时使用该值。

# 最佳实践

## 1. 错误处理

```ts
const { data, error } = await app.storage.from().upload("images/photo.jpg", file);

if (error) {
  console.error("操作失败:", error.message);
  return;
}
```

## 2. 文件路径规范

- 上传时传入云存储相对路径，例如 `images/photo.jpg`。
- 下载、删除、签名 URL 和文件信息查询建议使用上传返回的完整 `fileID`。
- 路径不要以 `/` 开头，不要包含连续 `/`。

# 相关资源

- [云存储 SDK 使用指南](../../../storage/sdk)
- [云存储权限管理](../../../storage/data-permission)
- [云存储安全规则](../../../storage/security-rules)
- [策略管理说明文档](./strategy)