关于 cloud-storage
面向应用开发者的 AWS S3 对象存储参考技能,涵盖文件上传、下载、存储桶配置和运行注意事项,附有 TypeScript、Python 与 Java 示例。
主要功能
- 01S3 文件上传与下载
- 02预签名 URL
- 03大文件分段上传
- 04存储桶策略与 CORS 示例
- 05故障排查和生产检查清单
适用场景
- 01让客户端直接向 S3 上传文件
- 02生成限时下载链接
- 03配置浏览器上传和公开资源访问
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ claude-dev-suite/ claude-dev-suite --skill cloud-storage安装后在 Claude Code 中输入/cloud-storage确认已加载。
详细介绍
云对象存储
AWS S3(默认推荐)
设置(SDK v3)
import { S3Client, PutObjectCommand, GetObjectCommand } from '@aws-sdk/client-s3';
import { getSignedUrl } from '@aws-sdk/s3-request-presigner';
const s3 = new S3Client({ region: process.env.AWS_REGION });
上传
// Direct upload
await s3.send(new PutObjectCommand({
Bucket: process.env.S3_BUCKET,
Key: `uploads/${userId}/${filename}`,
Body: buffer,
ContentType: mimeType,
Metadata: { 'uploaded-by': userId },
}));
// Presigned URL (client uploads directly to S3)
const url = await getSignedUrl(s3, new PutObjectCommand({
Bucket: process.env.S3_BUCKET,
Key: `uploads/${key}`,
ContentType: mimeType,
}), { expiresIn: 3600 });
预签名下载
const url = await getSignedUrl(s3, new GetObjectCommand({
Bucket: process.env.S3_BUCKET,
Key: fileKey,
}), { expiresIn: 3600 });
分段上传(大文件)
import { CreateMultipartUploadCommand, UploadPartCommand, CompleteMultipartUploadCommand } from '@aws-sdk/client-s3';
import { Upload } from '@aws-sdk/lib-storage';
// Simplified with lib-storage
const upload = new Upload({
client: s3,
params: { Bucket: bucket, Key: key, Body: readableStream },
partSize: 10 * 1024 * 1024, // 10MB parts
leavePartsOnError: false,
});
upload.on('httpUploadProgress', (progress) => console.log(progress));
await upload.done();
Python(boto3)
import boto3
from botocore.config import Config
s3 = boto3.client('s3', config=Config(signature_version='s3v4'))
# Upload
s3.upload_fileobj(file_obj, bucket, key, ExtraArgs={'ContentType': mime_type})
# Presigned URL
url = s3.generate_presigned_url('put_object',
Params={'Bucket': bucket, 'Key': key, 'ContentType': mime_type},
ExpiresIn=3600)
Java(Spring)
@Service
public class S3Service {
private final S3Client s3;
public String generatePresignedUploadUrl(String key, String contentType) {
var presigner = S3Presigner.builder().region(Region.of(region)).build();
var request = PutObjectPresignRequest.builder()
.signatureDuration(Duration.ofHours(1))
.putObjectRequest(b -> b.bucket(bucket).key(key).contentType(contentType))
.build();
return presigner.presignPutObject(request).url().toString();
}
}
存储桶策略模式
资源公开读取
{
"Statement": [{
"Effect": "Allow",
"Principal": "*",
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::my-bucket/public/*"
}]
}
直接上传的 CORS
{
"CORSRules": [{
"AllowedHeaders": ["*"],
"AllowedMethods": ["PUT", "POST"],
"AllowedOrigins": ["https://myapp.com"],
"ExposeHeaders": ["ETag"],
"MaxAgeSeconds": 3600
}]
}
反模式
| 反模式 | 修复方法 |
|---|---|
| 通过服务端代理大文件 | 使用预签名 URL,由客户端直接上传/下载 |
| 上传时未设置 Content-Type | 始终设置 ContentType,以确保浏览器行为正确 |
| 文件键可预测 | 使用 UUID 或哈希路径,以防止枚举 |
| 未设置生命周期规则 | 为临时上传设置过期时间,并将旧文件转储到 Glacier |
| 预签名 URL 有效期过长 | 缩短过期时间(上传为 1 小时,下载为 15 分钟) |
| 大文件未使用分段上传 | 对大于 100MB 的文件使用分段上传 |
快速故障排查
| 问题 | 原因 | 修复方法 |
|---|---|---|
| 预签名 URL 返回 403 | 已过期、区域错误或 CORS 配置问题 | 检查过期时间、区域和 CORS 配置 |
| SignatureDoesNotMatch | ContentType 不匹配 | 确保客户端发送的 ContentType 与签名时使用的一致 |
| 上传速度慢 | 大文件使用单段上传 | 使用并行分段进行分段上传 |
| CORS 错误 | 缺少 CORS 配置 | 为存储桶添加 CORSRules |
| 公开文件出现 AccessDenied | 缺少存储桶策略 | 为对应前缀添加公开读取策略 |
生产环境检查清单
- 所有客户端上传都使用预签名 URL(绝不通过代理传输大文件)
- 每次上传都设置 ContentType
- 使用基于 UUID 的键(防止枚举)
- 为直接上传配置 CORS
- 为临时文件/旧文件设置生命周期规则
- 启用静态加密(SSE-S3 或 SSE-KMS)
- 启用访问日志
- 为关键存储桶启用版本控制
- 为公开资源使用 CloudFront CDN
评论
支持 Markdown