存储系统
学习如何在 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 存储桶设置
-
创建 AWS 账户:在 aws.amazon.com 注册
-
创建 S3 存储桶:
- 转到 S3 对象存储
- 创建新存储桶
- 如有需要,禁用"阻止所有公共访问"
-
配置存储桶策略:
- 转到存储桶权限
- 添加以下策略:
{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Principal": "*", "Action": "s3:*", "Resource": "arn:aws:s3:::your-bucket-name/*" } ] }
-
配置 CORS:
- 转到存储桶 CORS
- 添加以下 CORS 配置:
[ { "AllowedHeaders": [ "*" ], "AllowedMethods": [ "PUT", "GET", "HEAD", "POST" ], "AllowedOrigins": [ "*" ], "ExposeHeaders": [], "MaxAgeSeconds": 3000 } ]
-
生成 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 存储桶设置
-
创建 Cloudflare 账户:在 cloudflare.com 注册
-
创建 R2 存储桶:
- 转到 R2 对象存储
- 创建新存储桶
- 为您的存储桶设置自定义域名
-
配置 CORS:
- 转到存储桶 CORS
- 添加以下 CORS 配置:
[ { "AllowedOrigins": [ "*" ], "AllowedMethods": [ "PUT", "GET", "HEAD" ], "AllowedHeaders": [ "Content-Type" // 必须设置 ], "ExposeHeaders": [], "MaxAgeSeconds": 3000 } ]
-
创建新的 API 令牌:
- 转到 R2/API/管理 API 令牌
- 点击
创建用户 API 令牌
- 将权限设置为存储桶的
对象读写
- 创建 API 令牌,获取
访问密钥 ID
和秘密访问密钥
📤 文件上传系统
服务器操作
NEXTDEVKIT 为文件操作提供服务器操作:
import { getSignedUploadUrl } from "@/storage/actions";
// 获取文件上传的签名 URL
const result = await getSignedUploadUrl({
bucket: appConfig.storage.bucketNames.avatars,
key: avatarKey,
contentType: file.type,
});
客户端文件上传
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 函数的调用。
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 的路由。
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 权限
- 检查存储桶策略
- 确保正确的身份验证
大文件上传:
- 对大文件使用分片上传
- 实现进度跟踪
- 设置适当的超时时间
🔗 相关资源
🎯 下一步
现在您了解了存储系统,请探索这些相关功能: