对象操作
📄 对象概述
对象(Object)是 S3 中存储的基本实体,由数据、元数据和唯一标识符(Key)组成。
🔑 对象组成
对象结构:
├── Key: 对象的唯一标识符(类似文件路径)
│ 例: images/2024/01/photo.jpg
├── Value: 对象数据(0 字节到 5TB)
├── Metadata: 元数据
│ ├── 系统元数据
│ │ - Content-Type
│ │ - Content-Length
│ │ - Last-Modified
│ │ - ETag (对象的 MD5 哈希)
│ └── 用户自定义元数据
│ - x-amz-meta-*
├── Version ID: 版本标识(如果启用版本控制)
├── Access Control: 访问控制信息
└── Storage Class: 存储类别⬆️ 上传对象
简单上传(< 5GB)
javascript
const AWS = require('aws-sdk');
const s3 = new AWS.S3();
const fs = require('fs');
// 上传本地文件
const uploadFile = async (bucketName, key, filePath) => {
const fileContent = fs.readFileSync(filePath);
const params = {
Bucket: bucketName,
Key: key,
Body: fileContent,
ContentType: 'image/jpeg',
Metadata: {
'author': 'John Doe',
'created-by': 'my-app'
}
};
try {
const result = await s3.putObject(params).promise();
console.log('Upload successful');
console.log('ETag:', result.ETag);
return result;
} catch (err) {
console.error('Upload failed:', err);
}
};
// 上传 Buffer
const uploadBuffer = async (bucketName, key, buffer) => {
const params = {
Bucket: bucketName,
Key: key,
Body: buffer,
ContentType: 'application/octet-stream'
};
await s3.putObject(params).promise();
};
// 上传 Stream
const uploadStream = async (bucketName, key, stream) => {
const params = {
Bucket: bucketName,
Key: key,
Body: stream
};
await s3.upload(params).promise();
};
// 上传并设置 ACL
const uploadPublicFile = async (bucketName, key, filePath) => {
const fileContent = fs.readFileSync(filePath);
const params = {
Bucket: bucketName,
Key: key,
Body: fileContent,
ACL: 'public-read', // 公开可读
ContentType: 'image/jpeg'
};
await s3.putObject(params).promise();
};使用 upload() 方法(推荐)
upload() 方法会自动处理分片上传和并发。
javascript
const uploadLargeFile = async (bucketName, key, filePath) => {
const fileStream = fs.createReadStream(filePath);
const params = {
Bucket: bucketName,
Key: key,
Body: fileStream,
ContentType: 'video/mp4'
};
const options = {
partSize: 10 * 1024 * 1024, // 10MB 每个分片
queueSize: 4 // 并发上传 4 个分片
};
try {
const upload = s3.upload(params, options);
// 监听进度
upload.on('httpUploadProgress', (progress) => {
const percentage = Math.round((progress.loaded / progress.total) * 100);
console.log(`Upload progress: ${percentage}%`);
});
const result = await upload.promise();
console.log('Upload completed:', result.Location);
return result;
} catch (err) {
console.error('Upload failed:', err);
}
};AWS CLI
bash
# 上传文件
aws s3 cp file.txt s3://my-bucket/file.txt
# 上传目录
aws s3 cp ./local-dir s3://my-bucket/remote-dir --recursive
# 设置元数据
aws s3 cp file.txt s3://my-bucket/file.txt \
--content-type "text/plain" \
--metadata author="John Doe"⬇️ 下载对象
下载到内存
javascript
// 获取对象内容
const getObject = async (bucketName, key) => {
const params = {
Bucket: bucketName,
Key: key
};
try {
const result = await s3.getObject(params).promise();
console.log('Content-Type:', result.ContentType);
console.log('Content-Length:', result.ContentLength);
console.log('Metadata:', result.Metadata);
// result.Body 是一个 Buffer
return result.Body;
} catch (err) {
if (err.code === 'NoSuchKey') {
console.error('Object does not exist');
} else {
console.error(err);
}
}
};
// 下载文本内容
const getTextContent = async (bucketName, key) => {
const result = await s3.getObject({
Bucket: bucketName,
Key: key
}).promise();
return result.Body.toString('utf-8');
};
// 下载 JSON 内容
const getJSONContent = async (bucketName, key) => {
const result = await s3.getObject({
Bucket: bucketName,
Key: key
}).promise();
return JSON.parse(result.Body.toString('utf-8'));
};下载到文件
javascript
const downloadFile = async (bucketName, key, localPath) => {
const params = {
Bucket: bucketName,
Key: key
};
try {
const result = await s3.getObject(params).promise();
fs.writeFileSync(localPath, result.Body);
console.log(`File downloaded to ${localPath}`);
} catch (err) {
console.error(err);
}
};
// 使用 Stream 下载大文件
const downloadLargeFile = (bucketName, key, localPath) => {
const params = {
Bucket: bucketName,
Key: key
};
const fileStream = fs.createWriteStream(localPath);
const s3Stream = s3.getObject(params).createReadStream();
s3Stream.pipe(fileStream);
s3Stream.on('error', (err) => {
console.error('Download error:', err);
});
fileStream.on('finish', () => {
console.log('Download completed');
});
};部分下载(Range)
javascript
// 下载对象的一部分
const downloadRange = async (bucketName, key, start, end) => {
const params = {
Bucket: bucketName,
Key: key,
Range: `bytes=${start}-${end}` // 例: bytes=0-1023
};
const result = await s3.getObject(params).promise();
return result.Body;
};
// 实现断点续传
const downloadWithResume = async (bucketName, key, localPath) => {
const stats = fs.existsSync(localPath) ? fs.statSync(localPath) : null;
const startByte = stats ? stats.size : 0;
// 获取对象总大小
const head = await s3.headObject({ Bucket: bucketName, Key: key }).promise();
const totalSize = head.ContentLength;
if (startByte >= totalSize) {
console.log('File already downloaded');
return;
}
const params = {
Bucket: bucketName,
Key: key,
Range: `bytes=${startByte}-${totalSize - 1}`
};
const result = await s3.getObject(params).promise();
fs.appendFileSync(localPath, result.Body);
console.log('Download resumed and completed');
};AWS CLI
bash
# 下载文件
aws s3 cp s3://my-bucket/file.txt ./file.txt
# 下载目录
aws s3 cp s3://my-bucket/remote-dir ./local-dir --recursive📋 列出对象
列出所有对象
javascript
// 使用 listObjectsV2(推荐)
const listObjects = async (bucketName, prefix = '') => {
const params = {
Bucket: bucketName,
Prefix: prefix,
MaxKeys: 1000
};
try {
const result = await s3.listObjectsV2(params).promise();
console.log(`Found ${result.Contents.length} objects`);
result.Contents.forEach(obj => {
console.log(`- ${obj.Key} (${obj.Size} bytes, ${obj.LastModified})`);
});
return result.Contents;
} catch (err) {
console.error(err);
}
};
// 列出所有对象(包括分页)
const listAllObjects = async (bucketName, prefix = '') => {
const allObjects = [];
let continuationToken = undefined;
do {
const params = {
Bucket: bucketName,
Prefix: prefix,
ContinuationToken: continuationToken,
MaxKeys: 1000
};
const result = await s3.listObjectsV2(params).promise();
allObjects.push(...result.Contents);
continuationToken = result.NextContinuationToken;
} while (continuationToken);
console.log(`Total objects: ${allObjects.length}`);
return allObjects;
};
// 按前缀分组列出(模拟目录结构)
const listByPrefix = async (bucketName, prefix = '', delimiter = '/') => {
const params = {
Bucket: bucketName,
Prefix: prefix,
Delimiter: delimiter
};
const result = await s3.listObjectsV2(params).promise();
console.log('Directories:');
result.CommonPrefixes?.forEach(p => {
console.log(` 📁 ${p.Prefix}`);
});
console.log('Files:');
result.Contents?.forEach(obj => {
console.log(` 📄 ${obj.Key}`);
});
return {
directories: result.CommonPrefixes || [],
files: result.Contents || []
};
};AWS CLI
bash
# 列出对象
aws s3 ls s3://my-bucket/
# 递归列出
aws s3 ls s3://my-bucket/ --recursive
# 显示大小和总计
aws s3 ls s3://my-bucket/ --recursive --human-readable --summarize📝 复制对象
同 Bucket 复制
javascript
const copyObject = async (bucketName, sourceKey, destKey) => {
const params = {
Bucket: bucketName,
CopySource: `${bucketName}/${sourceKey}`,
Key: destKey
};
try {
await s3.copyObject(params).promise();
console.log(`Copied from ${sourceKey} to ${destKey}`);
} catch (err) {
console.error(err);
}
};跨 Bucket 复制
javascript
const copyBetweenBuckets = async (sourceBucket, sourceKey, destBucket, destKey) => {
const params = {
Bucket: destBucket,
CopySource: `${sourceBucket}/${sourceKey}`,
Key: destKey
};
await s3.copyObject(params).promise();
};复制并修改元数据
javascript
const copyWithMetadata = async (bucketName, sourceKey, destKey) => {
const params = {
Bucket: bucketName,
CopySource: `${bucketName}/${sourceKey}`,
Key: destKey,
MetadataDirective: 'REPLACE', // 替换元数据
Metadata: {
'author': 'Jane Doe',
'version': '2.0'
},
ContentType: 'image/png'
};
await s3.copyObject(params).promise();
};🗑️ 删除对象
删除单个对象
javascript
const deleteObject = async (bucketName, key) => {
const params = {
Bucket: bucketName,
Key: key
};
try {
await s3.deleteObject(params).promise();
console.log(`Object ${key} deleted`);
} catch (err) {
console.error(err);
}
};批量删除
javascript
const deleteMultipleObjects = async (bucketName, keys) => {
const params = {
Bucket: bucketName,
Delete: {
Objects: keys.map(key => ({ Key: key })),
Quiet: false // false 返回删除结果
}
};
try {
const result = await s3.deleteObjects(params).promise();
console.log('Deleted:');
result.Deleted?.forEach(obj => {
console.log(` ✓ ${obj.Key}`);
});
if (result.Errors && result.Errors.length > 0) {
console.log('Errors:');
result.Errors.forEach(err => {
console.log(` ✗ ${err.Key}: ${err.Message}`);
});
}
return result;
} catch (err) {
console.error(err);
}
};
// 删除前缀下的所有对象
const deleteByPrefix = async (bucketName, prefix) => {
const objects = await listAllObjects(bucketName, prefix);
const keys = objects.map(obj => obj.Key);
// 分批删除(每批最多 1000 个)
const batchSize = 1000;
for (let i = 0; i < keys.length; i += batchSize) {
const batch = keys.slice(i, i + batchSize);
await deleteMultipleObjects(bucketName, batch);
}
console.log(`Deleted ${keys.length} objects`);
};🔍 检查对象
获取对象元数据(不下载内容)
javascript
const getObjectMetadata = async (bucketName, key) => {
const params = {
Bucket: bucketName,
Key: key
};
try {
const result = await s3.headObject(params).promise();
console.log('Metadata:');
console.log(' Content-Type:', result.ContentType);
console.log(' Content-Length:', result.ContentLength);
console.log(' ETag:', result.ETag);
console.log(' Last-Modified:', result.LastModified);
console.log(' Storage-Class:', result.StorageClass);
console.log(' Metadata:', result.Metadata);
return result;
} catch (err) {
if (err.code === 'NotFound') {
console.log('Object does not exist');
return null;
}
throw err;
}
};
// 检查对象是否存在
const objectExists = async (bucketName, key) => {
try {
await s3.headObject({ Bucket: bucketName, Key: key }).promise();
return true;
} catch (err) {
if (err.code === 'NotFound') {
return false;
}
throw err;
}
};📊 对象元数据
设置自定义元数据
javascript
// 上传时设置
const uploadWithMetadata = async (bucketName, key, data) => {
const params = {
Bucket: bucketName,
Key: key,
Body: data,
Metadata: {
'user-id': '12345',
'upload-source': 'mobile-app',
'processed': 'false'
}
};
await s3.putObject(params).promise();
};
// 更新现有对象的元数据(需要复制对象)
const updateMetadata = async (bucketName, key, newMetadata) => {
const params = {
Bucket: bucketName,
CopySource: `${bucketName}/${key}`,
Key: key,
MetadataDirective: 'REPLACE',
Metadata: newMetadata
};
await s3.copyObject(params).promise();
};💡 最佳实践
1. Key 命名
javascript
// ✅ 好的 Key 命名
'users/12345/avatars/2024-01-15-abc123.jpg'
'invoices/2024/01/INV-001.pdf'
'logs/application/2024/01/15/app.log'
// ❌ 避免
'用户头像.jpg' // 中文
'file (1).jpg' // 空格和括号
'../../../etc/passwd' // 路径穿越
// 性能优化:添加随机前缀避免热点分区
const hash = crypto.createHash('md5').update(userId).digest('hex').substring(0, 4);
const key = `${hash}/users/${userId}/avatar.jpg`;2. 分片上传阈值
javascript
// 文件大小 > 100MB 使用分片上传
const uploadFile = async (bucketName, key, filePath) => {
const stats = fs.statSync(filePath);
const fileSizeInBytes = stats.size;
const fileSizeInMB = fileSizeInBytes / (1024 * 1024);
if (fileSizeInMB > 100) {
// 使用分片上传
return uploadLargeFile(bucketName, key, filePath);
} else {
// 普通上传
const fileContent = fs.readFileSync(filePath);
return s3.putObject({
Bucket: bucketName,
Key: key,
Body: fileContent
}).promise();
}
};3. 设置正确的 Content-Type
javascript
const mime = require('mime-types');
const uploadWithCorrectContentType = async (bucketName, key, filePath) => {
const contentType = mime.lookup(filePath) || 'application/octet-stream';
const params = {
Bucket: bucketName,
Key: key,
Body: fs.readFileSync(filePath),
ContentType: contentType
};
await s3.putObject(params).promise();
};📖 相关资源
下一步:学习 SDK 使用 🛠️