关于 file-storage
这是一份使用 Tigris Object Storage 存储和提供文件的技能指南,涵盖 CLI 配置、`@tigrisdata/storage` SDK 操作及通过预签名 URL 实现浏览器上传。
主要功能
- 01配置 Tigris CLI 和凭据
- 02使用 SDK 上传和下载对象
- 03列出、查看和删除对象
- 04生成临时预签名 URL
- 05支持带进度的浏览器上传
适用场景
- 01为应用创建存储桶和访问密钥
- 02构建文件上传与下载流程
- 03让浏览器直传文件,避免服务器中转文件内容
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ tigrisdata/ skills --skill file-storage安装后在 Claude Code 中输入/file-storage确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
metadata.jsonREADME.mdresources/express.mdlaravel.mdnextjs.mdrails.mdremix.md详细介绍
Tigris 文件存储
使用 Tigris Object Storage 存储和提供文件。涵盖 CLI 设置(存储桶、访问密钥、身份验证)以及用于应用程序代码的 @tigrisdata/storage SDK。
快速开始
# 1. Install CLI & authenticate
npm install -g @tigrisdata/cli
tigris login
# 2. Create bucket and access key
tigris buckets create my-app-uploads
tigris access-keys create "my-app-uploads-key"
# ⚠ Save the Secret Access Key — shown only once
tigris access-keys assign tid_xxx --bucket my-app-uploads --role Editor
# 3. Install SDK
npm install @tigrisdata/storage
# .env
TIGRIS_STORAGE_ACCESS_KEY_ID=tid_xxx
TIGRIS_STORAGE_SECRET_ACCESS_KEY=tsec_yyy
TIGRIS_STORAGE_ENDPOINT=https://t3.storage.dev
TIGRIS_STORAGE_BUCKET=my-app-uploads
import { put } from "@tigrisdata/storage";
// Files are private by default — only authenticated requests can access them
const result = await put("avatars/user-123.jpg", file);
if (result.error) throw result.error;
console.log(result.data?.url);
// Use access: "public" only when anonymous users need direct URL access
// const result = await put("avatars/user-123.jpg", file, { access: "public" });
详细步骤请参阅下文的 使用 CLI 开始。
使用 CLI 开始
第 1 步:安装 CLI
npm install -g @tigrisdata/cli
验证安装:
tigris --version
t3 是 tigris 的别名——两者均可用于执行所有命令。
第 2 步:进行身份验证
tigris login
此操作会打开浏览器进行 OAuth 身份验证。登录后,使用以下命令进行验证:
tigris whoami
对于 CI/CD 或非交互式环境:
tigris configure --access-key <key> --access-secret <secret>
第 3 步:创建存储桶
tigris buckets create my-app-uploads
要点:
- 存储桶默认是私有的。使用
--public可让对象公开读取。 - 存储桶默认是全局的。使用
--locations可指定特定区域。 - 在任意命令后输入
help可查看其选项(例如,tigris buckets create help)。
第 4 步:创建访问密钥
tigris access-keys create "my-app-uploads-key"
此操作会输出 Access Key ID(tid_xxx)和 Secret Access Key(tsec_yyy)。
**Secret Access Key 只会显示一次。**请立即复制。Name 字段仅供人工识别,不会产生任何功能影响。
第 5 步:配置环境
在项目根目录中创建 .env:
TIGRIS_STORAGE_ACCESS_KEY_ID=tid_xxx
TIGRIS_STORAGE_SECRET_ACCESS_KEY=tsec_yyy
TIGRIS_STORAGE_ENDPOINT=https://t3.storage.dev
TIGRIS_STORAGE_BUCKET=my-app-uploads
TIGRIS_STORAGE_BUCKET 用于为所有 SDK 调用设置默认存储桶。将 .env 添加到 .gitignore 中——切勿提交凭据。
第 6 步:为存储桶分配访问密钥
tigris access-keys assign tid_xxx --bucket my-app-uploads --role Editor
角色:
| 角色 | 权限 | 适用场景 |
|---|---|---|
Editor | 读取、写入和删除对象 | 上传或删除文件的应用服务器 |
ReadOnly | 仅读取对象 | 仅提供或下载文件的应用 |
现在你已经拥有:
- 一个存储桶(
my-app-uploads) - 一个访问密钥(
tid_xxx/tsec_yyy) - 一个已分配给存储桶、具有 Editor 角色的密钥
- 一个可供 SDK 使用的
.env文件
第 7 步:安装 SDK
npm install @tigrisdata/storage
# or
yarn add @tigrisdata/storage
支持 ES Modules 和 CommonJS。
SDK 参考
所有方法均返回 TigrisStorageResponse<T, E>。务必先检查 error:
const result = await put("file.txt", "hello");
if (result.error) {
console.error(result.error);
return;
}
console.log(result.data);
config — 覆盖默认配置
每个方法都接受一个可选的 config 参数,其类型为 TigrisStorageConfig:
type TigrisStorageConfig = {
bucket?: string; // Override TIGRIS_STORAGE_BUCKET
accessKeyId?: string; // Override TIGRIS_STORAGE_ACCESS_KEY_ID
secretAccessKey?: string; // Override TIGRIS_STORAGE_SECRET_ACCESS_KEY
endpoint?: string; // Override TIGRIS_STORAGE_ENDPOINT
};
使用 config 可指定其他存储桶,或为每次调用使用不同的凭据:
// Upload to a different bucket
await put("report.pdf", data, { config: { bucket: "reports-archive" } });
// Use a separate read-only key for downloads
await get("file.txt", "string", { config: { accessKeyId: "tid_ro", secretAccessKey: "tsec_ro" } });
put — 上传
put(path: string, body: string | ReadableStream | Blob | Buffer, options?: PutOptions)
import { put } from "@tigrisdata/storage";
// Simple text upload
const result = await put("notes/hello.txt", "Hello, World!");
// Image with public access
const result = await put("avatars/user-123.jpg", file, {
access: "public",
contentType: "image/jpeg",
addRandomSuffix: false,
});
// Large file with multipart and progress
const result = await put("videos/demo.mp4", fileStream, {
multipart: true,
onUploadProgress: ({ loaded, total, percentage }) => {
console.log(`${loaded}/${total} bytes (${percentage}%)`);
},
});
// Prevent accidental overwrite
const result = await put("config.json", data, {
allowOverwrite: false,
});
Put 选项:
| 选项 | 值 | 默认值 | 用途 |
|---|---|---|---|
| access | public、private | private | 对象可见性 |
| addRandomSuffix | boolean | false | 为用户上传的文件添加随机后缀,避免同名文件冲突 |
| allowOverwrite | boolean | true | 允许替换现有文件 |
| contentType | MIME 字符串 | 自动推断 | Content-Type 标头 |
| contentDisposition | inline、attachment | inline | 浏览器显示行为 |
| multipart | boolean | false | 启用大文件上传 |
| onUploadProgress | 回调 | — | {loaded, total, percentage} |
| config | TigrisStorageConfig | — | 覆盖存储桶或凭据(见上文的 config 部分) |
响应数据:{ url, path, size, contentType, contentDisposition, modified }
get — 下载
get(path: string, format: "string" | "file" | "stream", options?: GetOptions)
import { get } from "@tigrisdata/storage";
// Read as string (text, JSON)
const result = await get("notes/hello.txt", "string");
console.log(result.data); // "Hello, World!"
// Serve as file (for API routes)
const result = await get("avatars/user-123.jpg", "file", {
contentDisposition: "inline",
});
// Trigger browser download
const result = await get("reports/q4.pdf", "file", {
contentDisposition: "attachment",
});
// Stream large files
const result = await get("videos/demo.mp4", "stream");
Get 选项:
| 选项 | 值 | 默认值 | 用途 |
|---|---|---|---|
| contentDisposition | inline,attachment | inline | 显示或下载 |
| contentType | MIME 字符串 | 上传时的值 | 覆盖内容类型 |
| encoding | 字符串 | utf-8 | 文本编码 |
| config | TigrisStorageConfig | — | 覆盖存储桶/凭据(参见上方的配置部分) |
remove — 删除
remove(path: string, options?: RemoveOptions)
import { remove } from "@tigrisdata/storage";
const result = await remove("notes/hello.txt");
if (result.error) {
console.error(result.error);
}
list — 列出对象
list(options?: ListOptions)
import { list } from "@tigrisdata/storage";
// List all objects
const result = await list();
console.log(result.data?.items);
// Filter by prefix
const result = await list({ prefix: "avatars/" });
// Paginate through all objects
const allFiles = [];
let page = await list({ limit: 100 });
allFiles.push(...(page.data?.items ?? []));
while (page.data?.hasMore) {
page = await list({
limit: 100,
paginationToken: page.data.paginationToken,
});
allFiles.push(...(page.data?.items ?? []));
}
List 选项:
| 选项 | 用途 |
|---|---|
| prefix | 筛选以此字符串开头的键 |
| delimiter | 对键分组(例如,用于文件夹的 "/") |
| limit | 每页的最大对象数(默认值:100) |
| paginationToken | 从上一页继续 |
| config | TigrisStorageConfig — 覆盖存储桶/凭据(参见上方的配置部分) |
响应数据: { items, paginationToken, hasMore }
head — 对象元数据
head(path: string, options?: HeadOptions)
import { head } from "@tigrisdata/storage";
const result = await head("avatars/user-123.jpg");
if (!result.error) {
console.log(result.data);
// { path, size, contentType, contentDisposition, modified, url }
}
getPresignedUrl — 临时 URL
getPresignedUrl(path: string, options: GetPresignedUrlOptions)
import { getPresignedUrl } from "@tigrisdata/storage";
// Temporary download link (1 hour)
const result = await getPresignedUrl("reports/q4.pdf", {
operation: "get",
expiresIn: 3600,
});
console.log(result.data?.url);
// Temporary upload link (10 minutes)
const result = await getPresignedUrl("uploads/photo.jpg", {
operation: "put",
expiresIn: 600,
});
预签名 URL 选项:
| 选项 | 值 | 默认值 | 用途 |
|---|---|---|---|
| operation | get,put | — | URL 用途 |
| expiresIn | 秒 | 3600 | 过期时间 |
| contentType | MIME 字符串 | — | PUT 必需 |
| config | TigrisStorageConfig | — | 覆盖存储桶/凭据(参见上方的配置部分) |
响应数据: { url, method, expiresIn }
客户端上传
无需通过服务器传输字节,即可直接从浏览器将文件上传到 Tigris。底层使用预签名 URL。
服务端 — 处理上传请求
// app/api/upload/route.ts
import { NextRequest, NextResponse } from "next/server";
import { handleClientUpload } from "@tigrisdata/storage";
export async function POST(request: NextRequest) {
try {
const body = await request.json();
const { data, error } = await handleClientUpload(body);
if (error) {
return NextResponse.json({ error: error.message }, { status: 500 });
}
return NextResponse.json({ data });
} catch (error) {
return NextResponse.json(
{ error: "Failed to process upload request" },
{ status: 500 },
);
}
}
客户端 — 直接上传
"use client";
import { upload } from "@tigrisdata/storage/client";
import { useState } from "react";
export default function FileUpload() {
const [progress, setProgress] = useState(0);
const [url, setUrl] = useState<string | null>(null);
const handleFileChange = async (e: React.ChangeEvent<HTMLInputElement>) => {
const file = e.target.files?.[0];
if (!file) return;
const result = await upload(file.name, file, {
url: "/api/upload",
access: "private",
multipart: true,
partSize: 10 * 1024 * 1024,
onUploadProgress: ({ percentage }) => {
setProgress(percentage);
},
});
setUrl(result.url);
};
return (
<>
<input type="file" onChange={handleFileChange} />
{progress > 0 && progress < 100 && <div>{progress}%</div>}
{url && <div>Uploaded: {url}</div>}
</>
);
}
客户端上传选项:
| 选项 | 必需 | 用途 |
|---|---|---|
| url | 是 | 用于获取预签名 URL 的后端端点 |
| access | 否 | public 或 private(默认值) |
| multipart | 否 | 为大文件启用 |
| partSize | 否 | 每个分块的字节数(默认值:5 MiB) |
| concurrency | 否 | 并行上传的分块数(默认值:4) |
| contentType | 否 | MIME 类型 |
| onUploadProgress | 否 | {loaded, total, percentage} |
React 组件(可选)
npm install @tigrisdata/react 提供可直接使用的 <Uploader> 组件,内置文件选择、进度显示和错误处理。有关用法,请参阅 @tigrisdata/react 文档。
关键规则
始终: 在读取 result.data 前检查 result.error | 默认将文件上传为 private — 仅在匿名用户需要直接访问 URL 时才设置 access: "public" | 浏览器上传时使用 handleClientUpload(不要通过服务器传输字节) | 对超过 100MB 的文件使用 multipart: true | 使用 hasMore + paginationToken 对 list() 进行分页 | 替换文件时删除旧文件(不会自动清理) | 在内容类型很重要时明确设置 contentType
切勿: 向客户端公开访问密钥(使用 handleClientUpload + @tigrisdata/storage/client 中的 upload()) | 跳过错误检查 | 使用 file.jpg 这类通用路径(使用 avatars/${userId}.jpg 或时间戳) | 忘记在创建时保存 Secret Access Key(它只显示一次)
已知问题
| 问题 | 原因与解决方法 |
|---|---|
| 上传时出现 "Access denied" | 密钥未分配给存储桶。运行 tigris access-keys assign tid_xxx --bucket <name> --role Editor |
| SDK 返回 "Bucket not found" | .env 中的存储桶名称错误。使用 tigris buckets list 验证 |
| Secret Access Key 丢失 | 无法恢复。创建新密钥:tigris access-keys create "new-key",然后重新分配 |
| 文件无法公开访问 | 存储桶默认是私有的。使用 --public 标志,或在 put() 中设置 access: "public" |
| 大文件上传卡住 | 对于超过 100MB 的文件,在 put 选项中添加 multipart: true |
| 列表返回的结果不完整 | 默认限制为 100。使用 hasMore 和 paginationToken 进行分页 |
| 客户端上传失败(CORS/500) | 服务端路由必须使用来自 @tigrisdata/storage 的 handleClientUpload |
CLI 快速参考
t3 是 tigris 的别名。在任何命令后输入 help 可查看选项。
# Auth
tigris login
tigris whoami
# Buckets
tigris buckets create <name> [--public] [--locations <region>]
tigris buckets list
tigris buckets delete <name>
# Access keys
tigris access-keys create "<name>"
tigris access-keys assign <tid_xxx> --bucket <name> --role Editor
# Objects
tigris cp <src> <dest> [-r] # Upload/download/copy
tigris mv <src> <dest> [-rf] # Move or rename
tigris rm <path> [-rf] # Delete
tigris ls [bucket/prefix] # List
tigris stat <path> # Metadata
tigris presign <path> # Presigned URL
tigris touch <path> # Create empty object
远程路径使用 t3:// 前缀:t3://my-bucket/path/file.txt
框架集成指南
如需了解特定框架的上传/下载模式,请阅读对应框架的资源文件:
| 框架 | SDK | 资源 |
|---|---|---|
| Next.js | @tigrisdata/storage(原生) | 阅读 ./resources/nextjs.md — Server Actions、API Routes、next/image、客户端上传 |
| Remix | @tigrisdata/storage(原生) | 阅读 ./resources/remix.md — action 函数、loaders、客户端上传 |
| Express | @tigrisdata/storage(原生) | 阅读 ./resources/express.md — Multer、流式上传、客户端上传 |
| Rails | aws-sdk-s3(暂时没有原生 Ruby SDK) | 阅读 ./resources/rails.md — Active Storage、直接上传、图像变体 |
| Django | tigris-boto3-ext + django-storages | 使用 tigris-python-sdk 技能 — 涵盖 FileField、django-storages、预签名 URL |
| Laravel | league/flysystem-aws-s3-v3(暂时没有原生 PHP SDK) | 阅读 ./resources/laravel.md — Storage facade、Livewire 上传、预签名 URL |
部署
| 框架 | 平台 | 设置环境变量的方式 |
|---|---|---|
| Next.js | Vercel | Dashboard → Settings → Environment Variables |
| Remix | Fly.io | fly secrets set TIGRIS_STORAGE_ACCESS_KEY_ID=... ... |
| Express | Docker | 使用 -e 标志或在 Compose 中配置 .env |
| Rails | Fly.io / Kamal | fly secrets set 或 kamal env push |
| Laravel | Forge / Vapor | Dashboard → Environment 或 vapor env:pull |
相关技能
- tigris-python-sdk — 通过 boto3 访问 Python、Django 上传、快照、分叉、Bundle API
- tigris-bucket-management — 高级存储桶选项(区域、层级、快照)
- tigris-object-operations — SDK 函数详细参考
- tigris-snapshots-forking — 时间点恢复和存储桶分叉