Logo文档

存储系统

学习如何在 NEXTDEVKIT 中使用 S3 兼容服务处理文件上传和存储

NEXTDEVKIT 包含一个使用 S3 兼容服务(如 AWS S3、Cloudflare R2 等)的强大存储系统,提供安全的文件上传、签名 URL 和灵活的存储管理。

🏗️ 存储系统架构

NEXTDEVKIT 的存储系统结构如下:

src/
├── storage/
│   ├── actions.ts              # 存储服务器操作
│   ├── types.ts                # 存储接口
│   └── providers/
│       ├── index.ts            # 提供商注册表
│       └── s3.ts               # S3 兼容提供商
├── config/
│   └── index.ts                # 存储配置

⚙️ 存储配置

应用配置

存储设置在 src/config/index.ts 中配置:

export const appConfig = {
  storage: {
    provider: "s3",           // 存储提供商(目前是 S3 兼容的)
    bucketNames: {
      avatars: process.env.NEXT_PUBLIC_AVATARS_BUCKET_NAME || "avatars",
      // 根据需要添加更多存储桶
    },
  },
  // ... 其他配置
} as const;

AWS S3 设置

环境变量

STORAGE_REGION=your_region # 例如 us-east-1
STORAGE_ACCESS_KEY_ID=your_access_key_id
STORAGE_SECRET_ACCESS_KEY=your_secret_access_key

# 存储桶名称(可选,可在应用配置中配置)
NEXT_PUBLIC_AVATARS_BUCKET_NAME=your-bucket-name

AWS S3 存储桶设置

  1. 创建 AWS 账户:在 aws.amazon.com 注册

  2. 创建 S3 存储桶

    • 转到 S3 对象存储
    • 创建新存储桶
    • 如有需要,禁用"阻止所有公共访问"
  3. 配置存储桶策略

    • 转到存储桶权限
    • 添加以下策略:
    {
     "Version": "2012-10-17",
     "Statement": [
         {
             "Effect": "Allow",
             "Principal": "*",
             "Action": "s3:*",
             "Resource": "arn:aws:s3:::your-bucket-name/*"
         }
       ]
     }
  4. 配置 CORS

    • 转到存储桶 CORS
    • 添加以下 CORS 配置:
    [
     {
         "AllowedHeaders": [
             "*"
         ],
         "AllowedMethods": [
             "PUT",
             "GET",
             "HEAD",
             "POST"
         ],
         "AllowedOrigins": [
             "*"
         ],
         "ExposeHeaders": [],
         "MaxAgeSeconds": 3000
     }
    ] 
  5. 生成 API 令牌

    • 创建具有 S3 权限的 API 令牌
    • 用作访问密钥凭证

Cloudflare R2 设置

环境变量

STORAGE_REGION=auto
STORAGE_ACCESS_KEY_ID=your_access_key_id
STORAGE_SECRET_ACCESS_KEY=your_secret_access_key
STORAGE_ENDPOINT=https://your-account-id.r2.cloudflarestorage.com

# 存储桶名称(可选,可在应用配置中配置)
NEXT_PUBLIC_AVATARS_BUCKET_NAME=your-bucket-name

Cloudflare R2 存储桶设置

  1. 创建 Cloudflare 账户:在 cloudflare.com 注册

  2. 创建 R2 存储桶

    • 转到 R2 对象存储
    • 创建新存储桶
    • 为您的存储桶设置自定义域名
  3. 配置 CORS

    • 转到存储桶 CORS
    • 添加以下 CORS 配置:
    [
       {
         "AllowedOrigins": [
           "*"
         ],
         "AllowedMethods": [
           "PUT",
           "GET",
           "HEAD"
         ],
         "AllowedHeaders": [
           "Content-Type" // 必须设置
         ],
         "ExposeHeaders": [],
         "MaxAgeSeconds": 3000
       }
     ]
  4. 创建新的 API 令牌:

    • 转到 R2/API/管理 API 令牌
    • 点击 创建用户 API 令牌
    • 将权限设置为存储桶的 对象读写
    • 创建 API 令牌,获取 访问密钥 ID秘密访问密钥

📤 文件上传系统

服务器操作

NEXTDEVKIT 为文件操作提供服务器操作:

src/storage/actions.ts
import { getSignedUploadUrl } from "@/storage/actions";

// 获取文件上传的签名 URL
const result = await getSignedUploadUrl({
  bucket: appConfig.storage.bucketNames.avatars,
  key: avatarKey,
  contentType: file.type,
});

客户端文件上传

src/components/settings/account/change-avatar-form.tsx
const result = await getSignedUploadUrl({
  bucket: appConfig.storage.bucketNames.avatars,
  key: avatarKey,
  contentType: file.type,
});

if (!result.data) {
  throw new Error("获取上传 URL 失败");
}

const uploadResponse = await fetch(result.data, {
  method: "PUT",
  body: file,
  headers: {
    "Content-Type": file.type,
  },
});

const { error: updateError } = await authClient.updateUser({
  image: avatarKey,
});

获取访问存储文件的签名 URL

要访问文件,您需要为文件创建签名 URL。

这可以按需进行,或者如果您正在从数据库中列出应该附有文件路径的文件条目,您可以为每个文件条目预先创建签名 URL。

您只需要在服务器操作中添加对存储包中 getSignedUrl 函数的调用。

src/storage/actions.ts
export const getSignedUrl = actionClient
	.inputSchema(z.object({
		bucket: z.string(),
		key: z.string(),
		expiresIn: z.number().optional().default(60 * 60),
	}))
	.outputSchema(z.string())
	.action(async ({ parsedInput: { bucket, key, expiresIn } }) => {
		const storageProvider = getStorageProvider();
		return await storageProvider.getSignedUrl({ bucket, key, expiresIn });
	});

如果您想避免每次都创建签名 URL,可以创建一个代理端点,该端点将自动为请求的文件创建签名 URL。此代理端点还应包括其他逻辑,如检查用户是否有权访问文件或添加缓存层。

例如,如果您想访问头像图像,可以创建一个将重定向到文件签名 URL 的路由。

src/app/image-proxy/[...path]/route.ts
if (bucket === appConfig.storage.bucketNames.avatars) {
  const signedUrl = await getStorageProvider().getSignedUrl({
    bucket,
    key: filePath,
    expiresIn: 60 * 60,
  });

  return NextResponse.redirect(signedUrl, {
    headers: { "Cache-Control": "max-age=3600" },
  });
}

🔧 故障排除

常见问题

上传失败

  • 检查 CORS 配置
  • 验证签名 URL 过期时间
  • 确保正确的文件权限
  • 检查网络连接

访问被拒绝

  • 验证 IAM 权限
  • 检查存储桶策略
  • 确保正确的身份验证

大文件上传

  • 对大文件使用分片上传
  • 实现进度跟踪
  • 设置适当的超时时间

🔗 相关资源


🎯 下一步

现在您了解了存储系统,请探索这些相关功能: