PG 模式云存储
CloudBase Flutter SDK 从 1.3.0 起提供 PG 模式云存储。通过 app.storage.fromBucket(bucketId) 获取指定 Bucket 的对象客户端。路径参数都是 Bucket 内对象名,不要传 cloud:// fileID。
from() 仍是传统模式入口。JS SDK 用 app.storage.from(bucketId) 进入 PG Bucket,Flutter SDK 用 fromBucket,避免和传统模式重名。
PG 模式与传统模式 API 的关键差异:
| 维度 | PG 模式 | 传统模式 |
|---|---|---|
| 入口 | app.storage.fromBucket(bucketId) | app.storage.from() |
| 路径参数 | Bucket 内对象名,如 user-123/avatar.png | 上传、复制、移动用相对路径;下载、删除、签名 URL 用 cloud:// fileID |
| 权限模型 | PostgreSQL RLS(Row Level Security,行级安全策略) | 内置权限 + JSON 安全规则 |
| Bucket 管理 | 支持 createBucket / listBuckets / getBucket / updateBucket / deleteBucket | 不支持 |
| 文件列表 | 支持 list() | 不支持 |
| 覆盖默认值 | upsert 默认 false | upsert 默认 true |
| 跨 Bucket 操作 | move() / copy() 支持 destinationBucket | 不支持 |
| 下载 | download() 返回文件字节 | getDownloadUrls() / createSignedUrl() |
| 公开 URL | getPublicUrl() 本地拼接,不发请求 | 不支持 |
| 对象信息 | 支持 info() / exists() | 不支持 |
详见:传统模式云存储
快速开始
final app = await CloudBase.init(
env: 'your-env-id',
accessKey: 'your-access-key',
);
final bucket = app.storage.fromBucket('avatars');
final uploaded = await bucket.upload(
'user-123/avatar.png',
fileBytes,
options: PgStorageUploadOptions(
contentType: 'image/png',
upsert: true,
),
);
if (!uploaded.isSuccess) {
print(uploaded.error?.message);
return;
}
final file = await bucket.download('user-123/avatar.png');
final signed = await bucket.createSignedUrl('user-123/avatar.png', 3600);
await bucket.remove(['user-123/avatar.png']);
API 方法
Bucket 客户端
- fromBucket() - 选择 Bucket,返回对象操作客户端
文件操作
- upload() - 上传文件
- update() - 覆盖已有文件
- download() - 下载文件字节
- remove() - 删除文件
- move() - 移动文件
- copy() - 复制文件
- list() - 列出 Bucket 中的对象
URL 管理
- createSignedUrl() - 创建签名 URL
- createSignedUrls() - 批量创建签名 URL
- getPublicUrl() - 获取公开 URL
- createSignedUploadUrl() - 创建上传签名 URL
- uploadToSignedUrl() - 凭签名 token 上传
文件信息
Bucket 管理
- Bucket 管理 - 创建、查询、更新、删除 Bucket
fromBucket
CloudBasePgStorageFileApi app.storage.fromBucket(String bucketId)
参数
PG Bucket ID,对应 `storage.buckets.id`。空值、长度超过 256,或含空白、`/`、`\`、`?`、`#`、`%` 时同步抛出 `StorageError`。
返回
PG 模式 Bucket 的对象操作客户端。
示例
final bucket = app.storage.fromBucket('avatars');
upload
上传文件。默认不覆盖同名对象。文件字节按二进制发送。
Future<StorageResponse<StorageUploadResult>> upload(
String path,
List<int> fileData, {
PgStorageUploadOptions? options,
})
class PgStorageUploadOptions {
String? cacheControl; // 秒数字符串,默认 3600,写成 Cache-Control: max-age=<值>
String? contentType; // 未传时为 text/plain;charset=UTF-8
Map<String, dynamic>? metadata; // base64(JSON),放进 x-metadata
bool upsert; // 默认 false,仅 upload 发送 x-upsert
Map<String, String>? headers;
}
class StorageUploadResult {
String? id;
String? path;
String? fullPath; // 服务端返回的对象 Key
}
参数
Bucket 内对象名,如 `user-123/avatar.png`。不要传 `cloud://` fileID。
文件字节。
上传选项。默认不覆盖同名对象。
返回
返回数据
错误信息。含 `code`、`message`、`requestId`、`status`。
示例
final result = await app.storage.fromBucket('avatars').upload(
'user-123/avatar.png',
fileBytes,
options: PgStorageUploadOptions(
contentType: 'image/png',
upsert: true,
metadata: {'usage': 'avatar'},
),
);
if (result.isSuccess) {
print(result.data?.fullPath);
} else {
print(result.error?.message);
}
update
覆盖已有文件。请求使用 PUT,不发送 x-upsert。
Future<StorageResponse<StorageUploadResult>> update(
String path,
List<int> fileData, {
PgStorageUploadOptions? options,
})
参数
Bucket 内对象名。
新的文件字节。
上传选项。`upsert` 不会发给服务端。
返回
更新结果。
错误信息。
示例
await app.storage.fromBucket('avatars').update(
'user-123/avatar.png',
fileBytes,
options: PgStorageUploadOptions(contentType: 'image/png'),
);
download
下载文件并返回字节。
Future<StorageResponse<List<int>>> download(
String path, {
PgStorageUrlOptions? options,
})
class PgStorageUrlOptions {
Object? download; // true 追加 download=;字符串作为下载文件名
String? cacheNonce; // 追加到 URL,用于绕过 CDN 缓存
}
参数
Bucket 内对象名。
下载查询参数。
返回
文件字节。
错误信息。
示例
final result = await app.storage.fromBucket('avatars').download(
'user-123/avatar.png',
options: PgStorageUrlOptions(download: 'avatar.png'),
);
if (result.isSuccess) {
print(result.data?.length);
}
remove
删除一个或多个文件。单次最多 100 个,超出后自动分批。任一批失败,整个调用失败。
Future<StorageResponse<List<PgRemovedObject>>> remove(List<String> paths)
class PgRemovedObject {
String name;
String bucketId;
String? ownerId;
String? id;
Map<String, dynamic>? metadata;
}
参数
Bucket 内对象名列表。
返回
删除成功的对象。
错误信息。
示例
await app.storage.fromBucket('avatars').remove(['user-123/avatar.png']);
move
移动文件。源对象会被删除,可以跨 Bucket。
Future<StorageResponse<PgStorageMessage>> move(
String fromPath,
String toPath, {
PgStorageDestinationOptions? options,
})
class PgStorageDestinationOptions {
String? destinationBucket; // 不传则与源 Bucket 相同
bool? upsert; // 为 true 时允许覆盖目标路径
bool? copyMetadata; // 仅 copy 使用
Map<String, dynamic>? metadata; // 仅 copy 使用
}
class PgStorageMessage {
String message;
}
参数
源对象名。
目标对象名。
目标 Bucket 与覆盖选项。`copyMetadata` 和 `metadata` 不会发给移动接口。
返回
返回数据
错误信息。
示例
await app.storage.fromBucket('avatars').move(
'user-123/avatar.png',
'user-123/archive/avatar.png',
);
copy
复制文件,可以跨 Bucket。
Future<StorageResponse<PgCopyResult>> copy(
String fromPath,
String toPath, {
PgStorageDestinationOptions? options,
})
class PgCopyResult {
String path; // 服务端返回的 Key
}
参数
源对象名。
目标对象名。
跨 Bucket、覆盖和元数据选项。
返回
返回数据
错误信息。
示例
await app.storage.fromBucket('avatars').copy(
'user-123/avatar.png',
'user-123/avatar-copy.png',
options: PgStorageDestinationOptions(
upsert: true,
copyMetadata: true,
),
);
createSignedUrl
创建临时访问链接。
Future<StorageResponse<PgSignedUrl>> createSignedUrl(
String path,
int expiresIn, {
PgStorageUrlOptions? options,
})
class PgSignedUrl {
String fullSignedUrl;
}
参数
Bucket 内对象名。
有效期,单位秒。
追加到签名 URL 的 `download`、`cacheNonce`。
返回
返回数据
错误信息。
示例
final result = await app.storage.fromBucket('avatars').createSignedUrl(
'user-123/avatar.png',
3600,
options: PgStorageUrlOptions(download: 'avatar.png'),
);
print(result.data?.fullSignedUrl);
createSignedUrls
批量创建临时访问链接。单次最多 500 个,超出后自动分批。
Future<StorageResponse<List<PgSignedUrlItem>>> createSignedUrls(
List<String> paths,
int expiresIn, {
PgStorageUrlOptions? options,
})
class PgSignedUrlItem {
String path;
String? fullSignedUrl; // 失败时为 null
String? error;
}
参数
Bucket 内对象名列表。
有效期,单位秒。
追加到每条签名 URL 的选项。
返回
批量签名结果。单项失败时 `fullSignedUrl` 为 null,`error` 为原因。
整批请求的错误信息。
示例
final result = await app.storage.fromBucket('avatars').createSignedUrls(
['user-123/avatar.png'],
3600,
);
getPublicUrl
拼接公开对象 URL,不发请求。对象能否访问取决于 Bucket 的 public 属性。
StorageResponse<PgPublicUrl> getPublicUrl(
String path, {
PgStorageUrlOptions? options,
})
class PgPublicUrl {
String publicUrl;
}
参数
Bucket 内对象名。
追加到公开 URL 的 `download`、`cacheNonce`。
返回
返回数据
示例
final result = app.storage.fromBucket('public-assets').getPublicUrl(
'logos/cloudbase.png',
);
print(result.data?.publicUrl);
createSignedUploadUrl
创建上传签名 URL 和 token。调用时需要登录态。
Future<StorageResponse<PgSignedUploadUrl>> createSignedUploadUrl(
String path, {
bool upsert = false,
})
class PgSignedUploadUrl {
String fullSignedUrl;
String token;
String path;
}
参数
Bucket 内对象名。
是否允许覆盖同名对象,默认 `false`。为 `true` 时发送 `x-upsert: true`。
返回
返回数据
错误信息。
示例
final bucket = app.storage.fromBucket('avatars');
final signed = await bucket.createSignedUploadUrl(
'user-123/avatar.png',
upsert: true,
);
if (signed.isSuccess) {
await bucket.uploadToSignedUrl(
'user-123/avatar.png',
signed.data!.token,
fileBytes,
);
}
uploadToSignedUrl
凭签名 token 上传,不携带登录态。
Future<StorageResponse<PgSignedUploadResult>> uploadToSignedUrl(
String path,
String token,
List<int> fileData, {
PgStorageUploadOptions? options,
})
class PgSignedUploadResult {
String path;
String fullPath;
}
参数
Bucket 内对象名,需与签名时的路径一致。
`createSignedUploadUrl` 返回的 token。
文件字节。
内容类型、缓存和元数据。此方法不发送 `x-upsert`。
返回
返回数据
错误信息。
示例
await bucket.uploadToSignedUrl(
'user-123/avatar.png',
token,
fileBytes,
options: PgStorageUploadOptions(contentType: 'image/png'),
);
info
获取文件元信息。
Future<StorageResponse<PgObjectInfo>> info(String path)
class PgObjectInfo {
String id;
String name;
String? version;
String bucketId;
int? size;
String? contentType;
String? cacheControl;
String? etag;
Map<String, dynamic>? metadata;
String? lastModified;
String? createdAt;
}
参数
Bucket 内对象名。
返回
对象元信息。服务端 snake_case 字段已映射为 camelCase。
错误信息。
示例
final result = await app.storage.fromBucket('avatars').info('user-123/avatar.png');
print(result.data?.size);
exists
检查文件是否存在。HTTP 400 和 404 视为不存在,data 为 false。
Future<StorageResponse<bool>> exists(String path)
参数
Bucket 内对象名。
返回
是否存在。
除 400、404 以外的错误。
示例
final result = await app.storage.fromBucket('avatars').exists('user-123/avatar.png');
print(result.data);
list
按前缀列出对象。path 不传表示 Bucket 根目录。前缀保留调用方传入的形态,包括末尾 /。
Future<StorageResponse<PgListObjectsResponse>> list([
String? path,
PgStorageListOptions? options,
])
class PgStorageListOptions {
int? limit;
String? cursor; // 上一页的 nextCursor
bool? withDelimiter; // 为 true 时用 / 区分文件夹和对象
PgStorageSortBy? sortBy;
}
class PgStorageSortBy {
String? column; // name、created_at、updated_at
String? order; // asc、desc
}
class PgListObjectsResponse {
List<PgListObjectsEntry> folders;
List<PgListObjectsEntry> objects;
bool hasNext;
String? nextCursor;
}
参数
路径前缀。不传表示 Bucket 根目录。
分页、分隔符和排序。
返回
返回数据
错误信息。
示例
final result = await app.storage.fromBucket('avatars').list(
'user-123/',
PgStorageListOptions(limit: 20, withDelimiter: true),
);
if (result.isSuccess) {
for (final object in result.data!.objects) {
print(object.name);
}
}
Bucket 管理
以下方法直接挂在 app.storage 上,只在 PG 模式环境可用。bucketId 的字符限制与 fromBucket 相同。
Future<StorageResponse<List<PgBucket>>> listBuckets({
PgListBucketsOptions? options,
})
Future<StorageResponse<PgBucket>> getBucket(String bucketId)
Future<StorageResponse<PgBucketName>> createBucket(
String bucketId, {
PgCreateBucketOptions? options,
})
Future<StorageResponse<PgStorageMessage>> updateBucket(
String bucketId,
PgUpdateBucketOptions options,
)
Future<StorageResponse<PgStorageMessage>> deleteBucket(String bucketId)
class PgCreateBucketOptions {
bool public; // 默认 false
String? type; // 仅创建时可传,目前为 STANDARD
Object? fileSizeLimit; // 数字为字节,字符串如 100MB,null 表示不限制
Object? allowedMimeTypes; // List<String>,null 表示不限制
}
class PgUpdateBucketOptions {
bool? public; // 未传则不更新
Object? fileSizeLimit; // 显式 null 表示取消限制
Object? allowedMimeTypes;
}
class PgListBucketsOptions {
int? limit;
int? offset;
String? sortColumn; // id、name、created_at、updated_at
String? sortOrder; // asc、desc
String? search;
}
class PgBucket {
String id;
String name;
String? ownerId;
bool public;
String? type;
int? fileSizeLimit; // 字节,null 表示不限制
List<String>? allowedMimeTypes;
String createdAt;
String updatedAt;
}
参数
Bucket ID。`createBucket` 同时把它作为 `id` 和 `name`。
创建、更新或列表参数。更新时未传的字段不会出现在请求体里,也不能修改 `type`。
返回
Bucket 操作结果。`deleteBucket` 只删除空 Bucket,Bucket 非空时请求失败。
错误信息。
示例
final created = await app.storage.createBucket(
'avatars',
options: PgCreateBucketOptions(
public: false,
type: 'STANDARD',
fileSizeLimit: '100MB',
allowedMimeTypes: ['image/png', 'image/jpeg'],
),
);
final buckets = await app.storage.listBuckets(
options: PgListBucketsOptions(limit: 20, offset: 0),
);
await app.storage.updateBucket(
'avatars',
PgUpdateBucketOptions(public: true),
);
最佳实践
错误处理
请求失败时,结果在 StorageResponse.error,不会抛异常。bucketId 非法,或对象路径包含 .. 时,调用会直接抛出 StorageError。
final result = await app.storage.fromBucket('avatars').upload(
'user-123/avatar.png',
fileBytes,
);
if (!result.isSuccess) {
print(result.error?.message);
return;
}
对象路径
先用 fromBucket(bucketId) 选定 Bucket,之后的路径都是 Bucket 内对象名。
- 不要传
cloud://fileID。 - SDK 会去掉路径首尾的
/,并合并连续的/。 list的前缀不走这套清理,末尾/会保留。- 路径中的
..会直接抛错。
常见问题
路径参数
fromBucket(bucketId) 已经指定 Bucket。方法里的 path 是 Bucket 内对象名,不需要也不应传 cloud:// fileID。
访问权限
PG 模式由 PostgreSQL storage.objects 和 storage.buckets 上的 RLS Policy 控制。返回 403 时,检查当前登录态、对象路径、bucketId 和对应策略。