跳转到内容
搜索文档

上传对象

最后更新 查看 MarkdownAgent 设置

有多种方式将对象上传到 R2。选择哪种方式取决于对象大小和性能要求。

选择上传方式

单次上传 (PUT) 分片上传
适用场景 中小型文件(约 100 MB 以下) 大文件,或需要并行上传和断点续传的场景
最大对象大小 5 GiB 5 TiB(最多 10,000 个分片)
分片大小 不适用 每个分片 5 MiB – 5 GiB
可续传 否 — 必须重新开始整个上传 是 — 只需重试失败的分片
并行上传 是 — 可以并发上传分片
何时使用 快速、简单地上传小对象 视频、备份、数据集或任何可靠性至关重要的文件

通过仪表板上传

从 Cloudflare 仪表板向存储桶上传对象:

  1. 在 Cloudflare 仪表板中,前往 R2 object storage(R2 对象存储) 页面。

    Go to Overview ↗
  2. 选择您的存储桶。

  3. 选择 Upload(上传)

  4. 将文件拖放到上传区域,或 select from computer

上传成功后会显示确认消息。

创建文件夹

您还可以通过选择 Create folder(创建文件夹) 从仪表板创建文件夹。这会创建一个以 / 结尾的零字节对象作为占位符。有关 R2 中文件夹工作原理的更多信息,请参阅前缀和文件夹

通过 Workers API 上传

在 Workers 中使用 R2 绑定 在服务端上传对象。有关设置 R2 绑定的说明,请参阅从 Workers 使用 R2

单次上传

使用 put() 在单个请求中上传对象。这是上传中小型对象的最简单方式。

export default {
	async fetch(request, env) {
		try {
			const object = await env.MY_BUCKET.put("image.png", request.body, {
				httpMetadata: {
					contentType: "image/png",
				},
			});

			if (object === null) {
				return new Response("Precondition failed or upload returned null", {
					status: 412,
				});
			}

			return Response.json({
				key: object.key,
				size: object.size,
				etag: object.etag,
			});
		} catch (err) {
			return new Response(`Upload failed: ${err}`, { status: 500 });
		}
	},
};
export default {
	async fetch(request: Request, env: Env): Promise<Response> {
		try {
			const object = await env.MY_BUCKET.put("image.png", request.body, {
				httpMetadata: {
					contentType: "image/png",
				},
			});

			if (object === null) {
				return new Response("Precondition failed or upload returned null", {
					status: 412,
				});
			}

			return Response.json({
				key: object.key,
				size: object.size,
				etag: object.etag,
			});
		} catch (err) {
			return new Response(`Upload failed: ${err}`, { status: 500 });
		}
	},
} satisfies ExportedHandler<Env>;

分片上传

对于大文件或需要并行上传分片的场景,请使用 createMultipartUpload()resumeMultipartUpload()。每个分片至少为 5 MiB(最后一个分片除外)。

export default {
	async fetch(request, env) {
		const key = "large-file.bin";

		// Create a new multipart upload
		const multipartUpload = await env.MY_BUCKET.createMultipartUpload(key);

		try {
			// In a real application, these would be actual data chunks.
			// Each part except the last must be at least 5 MiB.
			const firstChunk = new Uint8Array(5 * 1024 * 1024); // placeholder
			const secondChunk = new Uint8Array(1024); // placeholder

			const part1 = await multipartUpload.uploadPart(1, firstChunk);
			const part2 = await multipartUpload.uploadPart(2, secondChunk);

			// Complete the upload with all parts
			const object = await multipartUpload.complete([part1, part2]);

			return Response.json({
				key: object.key,
				etag: object.httpEtag,
			});
		} catch (err) {
			// Abort on failure so incomplete uploads do not count against storage
			await multipartUpload.abort();
			return new Response(`Multipart upload failed: ${err}`, { status: 500 });
		}
	},
};
export default {
	async fetch(request: Request, env: Env): Promise<Response> {
		const key = "large-file.bin";

		// Create a new multipart upload
		const multipartUpload = await env.MY_BUCKET.createMultipartUpload(key);

		try {
			// In a real application, these would be actual data chunks.
			// Each part except the last must be at least 5 MiB.
			const firstChunk = new Uint8Array(5 * 1024 * 1024); // placeholder
			const secondChunk = new Uint8Array(1024); // placeholder

			const part1 = await multipartUpload.uploadPart(1, firstChunk);
			const part2 = await multipartUpload.uploadPart(2, secondChunk);

			// Complete the upload with all parts
			const object = await multipartUpload.complete([part1, part2]);

			return Response.json({
				key: object.key,
				etag: object.httpEtag,
			});
		} catch (err) {
			// Abort on failure so incomplete uploads do not count against storage
			await multipartUpload.abort();
			return new Response(`Multipart upload failed: ${err}`, { status: 500 });
		}
	},
} satisfies ExportedHandler<Env>;

在大多数情况下,分片状态(uploadId 和已上传分片的 ETag)由向 Worker 发送请求的客户端跟踪。以下示例公开了一个 HTTP API,客户端应用程序可以调用它来创建、上传分片并完成分片上传:

export default {
	async fetch(request, env) {
		const url = new URL(request.url);
		const key = url.pathname.slice(1);
		const action = url.searchParams.get("action");

		if (!key || !action) {
			return new Response("Missing key or action", { status: 400 });
		}

		switch (action) {
			// Step 1: Client calls POST /<key>?action=mpu-create
			case "mpu-create": {
				const upload = await env.MY_BUCKET.createMultipartUpload(key);
				return Response.json({ key: upload.key, uploadId: upload.uploadId });
			}

			// Step 2: Client calls PUT /<key>?action=mpu-uploadpart&uploadId=...&partNumber=...
			case "mpu-uploadpart": {
				const uploadId = url.searchParams.get("uploadId");
				const partNumber = Number(url.searchParams.get("partNumber"));
				if (!uploadId || !partNumber || !request.body) {
					return new Response("Missing uploadId, partNumber, or body", {
						status: 400,
					});
				}
				const upload = env.MY_BUCKET.resumeMultipartUpload(key, uploadId);
				try {
					const part = await upload.uploadPart(partNumber, request.body);
					return Response.json(part);
				} catch (err) {
					return new Response(String(err), { status: 400 });
				}
			}

			// Step 3: Client calls POST /<key>?action=mpu-complete&uploadId=...
			case "mpu-complete": {
				const uploadId = url.searchParams.get("uploadId");
				if (!uploadId) {
					return new Response("Missing uploadId", { status: 400 });
				}
				const upload = env.MY_BUCKET.resumeMultipartUpload(key, uploadId);
				const body = await request.json();
				try {
					const object = await upload.complete(body.parts);
					return new Response(null, {
						headers: { etag: object.httpEtag },
					});
				} catch (err) {
					return new Response(String(err), { status: 400 });
				}
			}

			// Abort an in-progress upload
			case "mpu-abort": {
				const uploadId = url.searchParams.get("uploadId");
				if (!uploadId) {
					return new Response("Missing uploadId", { status: 400 });
				}
				const upload = env.MY_BUCKET.resumeMultipartUpload(key, uploadId);
				try {
					await upload.abort();
				} catch (err) {
					return new Response(String(err), { status: 400 });
				}
				return new Response(null, { status: 204 });
			}

			default:
				return new Response(`Unknown action: ${action}`, { status: 400 });
		}
	},
};
export default {
	async fetch(request: Request, env: Env): Promise<Response> {
		const url = new URL(request.url);
		const key = url.pathname.slice(1);
		const action = url.searchParams.get("action");

		if (!key || !action) {
			return new Response("Missing key or action", { status: 400 });
		}

		switch (action) {
			// Step 1: Client calls POST /<key>?action=mpu-create
			case "mpu-create": {
				const upload = await env.MY_BUCKET.createMultipartUpload(key);
				return Response.json({ key: upload.key, uploadId: upload.uploadId });
			}

			// Step 2: Client calls PUT /<key>?action=mpu-uploadpart&uploadId=...&partNumber=...
			case "mpu-uploadpart": {
				const uploadId = url.searchParams.get("uploadId");
				const partNumber = Number(url.searchParams.get("partNumber"));
				if (!uploadId || !partNumber || !request.body) {
					return new Response("Missing uploadId, partNumber, or body", {
						status: 400,
					});
				}
				const upload = env.MY_BUCKET.resumeMultipartUpload(key, uploadId);
				try {
					const part = await upload.uploadPart(partNumber, request.body);
					return Response.json(part);
				} catch (err) {
					return new Response(String(err), { status: 400 });
				}
			}

			// Step 3: Client calls POST /<key>?action=mpu-complete&uploadId=...
			case "mpu-complete": {
				const uploadId = url.searchParams.get("uploadId");
				if (!uploadId) {
					return new Response("Missing uploadId", { status: 400 });
				}
				const upload = env.MY_BUCKET.resumeMultipartUpload(key, uploadId);
				const body = await request.json<{ parts: R2UploadedPart[] }>();
				try {
					const object = await upload.complete(body.parts);
					return new Response(null, {
						headers: { etag: object.httpEtag },
					});
				} catch (err) {
					return new Response(String(err), { status: 400 });
				}
			}

			// Abort an in-progress upload
			case "mpu-abort": {
				const uploadId = url.searchParams.get("uploadId");
				if (!uploadId) {
					return new Response("Missing uploadId", { status: 400 });
				}
				const upload = env.MY_BUCKET.resumeMultipartUpload(key, uploadId);
				try {
					await upload.abort();
				} catch (err) {
					return new Response(String(err), { status: 400 });
				}
				return new Response(null, { status: 204 });
			}

			default:
				return new Response(`Unknown action: ${action}`, { status: 400 });
		}
	},
} satisfies ExportedHandler<Env>;

完整的 Workers API 参考请参阅 Workers API 参考

预签名 URL(Workers)

当您需要客户端(浏览器、移动应用)直接上传到 R2 而无需通过 Worker 代理时,请在服务端生成预签名 URL 并将其交给客户端:

import { AwsClient } from "aws4fetch";

export default {
	async fetch(request, env) {
		const r2 = new AwsClient({
			accessKeyId: env.R2_ACCESS_KEY_ID,
			secretAccessKey: env.R2_SECRET_ACCESS_KEY,
		});

		// Generate a presigned PUT URL valid for 1 hour
		const url = new URL(
			"https://<ACCOUNT_ID>.r2.cloudflarestorage.com/my-bucket/image.png",
		);
		url.searchParams.set("X-Amz-Expires", "3600");

		const signed = await r2.sign(new Request(url, { method: "PUT" }), {
			aws: { signQuery: true },
		});

		// Return the signed URL to the client — they can PUT directly to R2
		return Response.json({ url: signed.url });
	},
};
import { AwsClient } from "aws4fetch";

interface Env {
	R2_ACCESS_KEY_ID: string;
	R2_SECRET_ACCESS_KEY: string;
}

export default {
	async fetch(request: Request, env: Env): Promise<Response> {
		const r2 = new AwsClient({
			accessKeyId: env.R2_ACCESS_KEY_ID,
			secretAccessKey: env.R2_SECRET_ACCESS_KEY,
		});

		// Generate a presigned PUT URL valid for 1 hour
		const url = new URL(
			"https://<ACCOUNT_ID>.r2.cloudflarestorage.com/my-bucket/image.png",
		);
		url.searchParams.set("X-Amz-Expires", "3600");

		const signed = await r2.sign(new Request(url, { method: "PUT" }), {
			aws: { signQuery: true },
		});

		// Return the signed URL to the client — they can PUT directly to R2
		return Response.json({ url: signed.url });
	},
} satisfies ExportedHandler<Env>;

有关预签名 URL 的完整文档(包括 GET、PUT 和安全最佳实践),请参阅预签名 URL

通过 S3 API 上传

使用 S3 兼容 SDK 上传对象。您需要 账户 IDR2 API 令牌

单次上传

import { S3Client, PutObjectCommand } from "@aws-sdk/client-s3";
import { readFile } from "node:fs/promises";

const S3 = new S3Client({
	region: "auto",
	endpoint: `https://<ACCOUNT_ID>.r2.cloudflarestorage.com`,
	credentials: {
		accessKeyId: "<ACCESS_KEY_ID>",
		secretAccessKey: "<SECRET_ACCESS_KEY>",
	},
});

const fileContent = await readFile("./image.png");

const response = await S3.send(
	new PutObjectCommand({
		Bucket: "my-bucket",
		Key: "image.png",
		Body: fileContent,
		ContentType: "image/png",
	}),
);
console.log(`Uploaded successfully. ETag: ${response.ETag}`);
import { S3Client, PutObjectCommand } from "@aws-sdk/client-s3";
import { readFile } from "node:fs/promises";

const S3 = new S3Client({
	region: "auto",
	endpoint: `https://<ACCOUNT_ID>.r2.cloudflarestorage.com`,
	credentials: {
		accessKeyId: "<ACCESS_KEY_ID>",
		secretAccessKey: "<SECRET_ACCESS_KEY>",
	},
});

const fileContent = await readFile("./image.png");

const response = await S3.send(
	new PutObjectCommand({
		Bucket: "my-bucket",
		Key: "image.png",
		Body: fileContent,
		ContentType: "image/png",
	}),
);
console.log(`Uploaded successfully. ETag: ${response.ETag}`);
import boto3

s3 = boto3.client(
    service_name="s3",
    endpoint_url="https://<ACCOUNT_ID>.r2.cloudflarestorage.com",
    aws_access_key_id="<ACCESS_KEY_ID>",
    aws_secret_access_key="<SECRET_ACCESS_KEY>",
    region_name="auto",
)

with open("./image.png", "rb") as f:
    response = s3.put_object(
        Bucket="my-bucket",
        Key="image.png",
        Body=f,
        ContentType="image/png",
    )
    print(f"Uploaded successfully. ETag: {response['ETag']}")

分片上传

当文件超过可配置的阈值时,大多数 S3 SDK 会自动处理分片上传。以下示例展示了自动(高级)和手动(低级)两种方式。

自动分片上传

SDK 会拆分文件并并行上传分片。

import { S3Client } from "@aws-sdk/client-s3";
import { Upload } from "@aws-sdk/lib-storage";
import { createReadStream } from "node:fs";

const S3 = new S3Client({
	region: "auto",
	endpoint: `https://<ACCOUNT_ID>.r2.cloudflarestorage.com`,
	credentials: {
		accessKeyId: "<ACCESS_KEY_ID>",
		secretAccessKey: "<SECRET_ACCESS_KEY>",
	},
});

const upload = new Upload({
	client: S3,
	params: {
		Bucket: "my-bucket",
		Key: "large-file.bin",
		Body: createReadStream("./large-file.bin"),
	},
	// Upload parts in parallel (default: 4)
	leavePartsOnError: false,
});

upload.on("httpUploadProgress", (progress) => {
	console.log(`Uploaded ${progress.loaded ?? 0} bytes`);
});

const result = await upload.done();
console.log(`Upload complete. ETag: ${result.ETag}`);
import { S3Client } from "@aws-sdk/client-s3";
import { Upload } from "@aws-sdk/lib-storage";
import { createReadStream } from "node:fs";

const S3 = new S3Client({
	region: "auto",
	endpoint: `https://<ACCOUNT_ID>.r2.cloudflarestorage.com`,
	credentials: {
		accessKeyId: "<ACCESS_KEY_ID>",
		secretAccessKey: "<SECRET_ACCESS_KEY>",
	},
});

const upload = new Upload({
	client: S3,
	params: {
		Bucket: "my-bucket",
		Key: "large-file.bin",
		Body: createReadStream("./large-file.bin"),
	},
	leavePartsOnError: false,
});

upload.on("httpUploadProgress", (progress) => {
	console.log(`Uploaded ${progress.loaded ?? 0} bytes`);
});

const result = await upload.done();
console.log(`Upload complete. ETag: ${result.ETag}`);
import boto3

s3 = boto3.client(
    service_name="s3",
    endpoint_url="https://<ACCOUNT_ID>.r2.cloudflarestorage.com",
    aws_access_key_id="<ACCESS_KEY_ID>",
    aws_secret_access_key="<SECRET_ACCESS_KEY>",
    region_name="auto",
)

# upload_file automatically uses multipart for large files.
# For better throughput with large objects, use the manual multipart example below.
s3.upload_file(
    Filename="./large-file.bin",
    Bucket="my-bucket",
    Key="large-file.bin",
)

手动分片上传

当您需要完全控制分片大小或上传顺序时,请使用低级 API。

import {
	S3Client,
	CreateMultipartUploadCommand,
	UploadPartCommand,
	CompleteMultipartUploadCommand,
	AbortMultipartUploadCommand,
	type CompletedPart,
} from "@aws-sdk/client-s3";
import { createReadStream, statSync } from "node:fs";

const S3 = new S3Client({
	region: "auto",
	endpoint: `https://<ACCOUNT_ID>.r2.cloudflarestorage.com`,
	credentials: {
		accessKeyId: "<ACCESS_KEY_ID>",
		secretAccessKey: "<SECRET_ACCESS_KEY>",
	},
});

const bucket = "my-bucket";
const key = "large-file.bin";
const partSize = 10 * 1024 * 1024; // 10 MiB per part

// Step 1: Create the multipart upload
const { UploadId } = await S3.send(
	new CreateMultipartUploadCommand({ Bucket: bucket, Key: key }),
);

try {
	const fileSize = statSync("./large-file.bin").size;
	const partCount = Math.ceil(fileSize / partSize);
	const parts: CompletedPart[] = [];

	// Step 2: Upload each part
	for (let i = 0; i < partCount; i++) {
		const start = i * partSize;
		const end = Math.min(start + partSize, fileSize);
		const { ETag } = await S3.send(
			new UploadPartCommand({
				Bucket: bucket,
				Key: key,
				UploadId,
				PartNumber: i + 1,
				Body: createReadStream("./large-file.bin", { start, end: end - 1 }),
				ContentLength: end - start,
			}),
		);
		parts.push({ PartNumber: i + 1, ETag });
	}

	// Step 3: Complete the upload
	await S3.send(
		new CompleteMultipartUploadCommand({
			Bucket: bucket,
			Key: key,
			UploadId,
			MultipartUpload: { Parts: parts },
		}),
	);
	console.log("Multipart upload complete.");
} catch (err) {
	// Abort on failure to clean up incomplete parts
	try {
		await S3.send(
			new AbortMultipartUploadCommand({ Bucket: bucket, Key: key, UploadId }),
		);
	} catch (_abortErr) {
		// Best-effort cleanup — the original error is more important
	}
	throw err;
}
import {
	S3Client,
	CreateMultipartUploadCommand,
	UploadPartCommand,
	CompleteMultipartUploadCommand,
	AbortMultipartUploadCommand,
} from "@aws-sdk/client-s3";
import { createReadStream, statSync } from "node:fs";

const S3 = new S3Client({
	region: "auto",
	endpoint: `https://<ACCOUNT_ID>.r2.cloudflarestorage.com`,
	credentials: {
		accessKeyId: "<ACCESS_KEY_ID>",
		secretAccessKey: "<SECRET_ACCESS_KEY>",
	},
});

const bucket = "my-bucket";
const key = "large-file.bin";
const partSize = 10 * 1024 * 1024; // 10 MiB per part

// Step 1: Create the multipart upload
const { UploadId } = await S3.send(
	new CreateMultipartUploadCommand({ Bucket: bucket, Key: key }),
);

try {
	const fileSize = statSync("./large-file.bin").size;
	const partCount = Math.ceil(fileSize / partSize);
	const parts = [];

	// Step 2: Upload each part
	for (let i = 0; i < partCount; i++) {
		const start = i * partSize;
		const end = Math.min(start + partSize, fileSize);
		const { ETag } = await S3.send(
			new UploadPartCommand({
				Bucket: bucket,
				Key: key,
				UploadId,
				PartNumber: i + 1,
				Body: createReadStream("./large-file.bin", { start, end: end - 1 }),
				ContentLength: end - start,
			}),
		);
		parts.push({ PartNumber: i + 1, ETag });
	}

	// Step 3: Complete the upload
	await S3.send(
		new CompleteMultipartUploadCommand({
			Bucket: bucket,
			Key: key,
			UploadId,
			MultipartUpload: { Parts: parts },
		}),
	);
	console.log("Multipart upload complete.");
} catch (err) {
	// Abort on failure to clean up incomplete parts
	try {
		await S3.send(
			new AbortMultipartUploadCommand({ Bucket: bucket, Key: key, UploadId }),
		);
	} catch (_abortErr) {
		// Best-effort cleanup — the original error is more important
	}
	throw err;
}
import boto3
import math
import os
from concurrent.futures import ThreadPoolExecutor

s3 = boto3.client(
    service_name="s3",
    endpoint_url="https://<ACCOUNT_ID>.r2.cloudflarestorage.com",
    aws_access_key_id="<ACCESS_KEY_ID>",
    aws_secret_access_key="<SECRET_ACCESS_KEY>",
    region_name="auto",
)

bucket = "my-bucket"
key = "large-file.bin"
file_path = "./large-file.bin"
part_size = 16 * 1024 * 1024  # 16 MiB per part
max_workers = 10  # Number of parallel upload threads

# Step 1: Create the multipart upload
upload_id = None
mpu = s3.create_multipart_upload(Bucket=bucket, Key=key)
upload_id = mpu["UploadId"]

def upload_part(part_number, data):
    response = s3.upload_part(
        Bucket=bucket,
        Key=key,
        UploadId=upload_id,
        PartNumber=part_number,
        Body=data,
    )
    return {"PartNumber": part_number, "ETag": response["ETag"]}

try:
    file_size = os.path.getsize(file_path)
    part_count = math.ceil(file_size / part_size)

    # Step 2: Upload parts in parallel
    with ThreadPoolExecutor(max_workers=max_workers) as pool:
        futures = []
        with open(file_path, "rb") as f:
            for i in range(part_count):
                data = f.read(part_size)
                futures.append(pool.submit(upload_part, i + 1, data))

        parts = [future.result() for future in futures]

    # Step 3: Complete the upload
    s3.complete_multipart_upload(
        Bucket=bucket,
        Key=key,
        UploadId=upload_id,
        MultipartUpload={"Parts": parts},
    )
    print("Multipart upload complete.")
except Exception:
    # Abort on failure to clean up incomplete parts
    if upload_id:
        try:
            s3.abort_multipart_upload(Bucket=bucket, Key=key, UploadId=upload_id)
        except Exception:
            pass  # Best-effort cleanup — the original error is more important
    raise

预签名 URL(S3 API)

对于用户直接上传到 R2 而无需经过服务端的客户端上传场景,请生成预签名 PUT URL。您的服务端创建 URL,客户端向该 URL 上传 — 客户端不会接触到 API 凭据。

import { S3Client, PutObjectCommand } from "@aws-sdk/client-s3";
import { getSignedUrl } from "@aws-sdk/s3-request-presigner";

const S3 = new S3Client({
	region: "auto",
	endpoint: `https://<ACCOUNT_ID>.r2.cloudflarestorage.com`,
	credentials: {
		accessKeyId: "<ACCESS_KEY_ID>",
		secretAccessKey: "<SECRET_ACCESS_KEY>",
	},
});

const presignedUrl = await getSignedUrl(
	S3,
	new PutObjectCommand({
		Bucket: "my-bucket",
		Key: "user-upload.png",
		ContentType: "image/png",
	}),
	{ expiresIn: 3600 }, // Valid for 1 hour
);

console.log(presignedUrl);
// Return presignedUrl to the client
import { S3Client, PutObjectCommand } from "@aws-sdk/client-s3";
import { getSignedUrl } from "@aws-sdk/s3-request-presigner";

const S3 = new S3Client({
	region: "auto",
	endpoint: `https://<ACCOUNT_ID>.r2.cloudflarestorage.com`,
	credentials: {
		accessKeyId: "<ACCESS_KEY_ID>",
		secretAccessKey: "<SECRET_ACCESS_KEY>",
	},
});

const presignedUrl = await getSignedUrl(
	S3,
	new PutObjectCommand({
		Bucket: "my-bucket",
		Key: "user-upload.png",
		ContentType: "image/png",
	}),
	{ expiresIn: 3600 }, // Valid for 1 hour
);

console.log(presignedUrl);
// Return presignedUrl to the client
import boto3

s3 = boto3.client(
    service_name="s3",
    endpoint_url="https://<ACCOUNT_ID>.r2.cloudflarestorage.com",
    aws_access_key_id="<ACCESS_KEY_ID>",
    aws_secret_access_key="<SECRET_ACCESS_KEY>",
    region_name="auto",
)

presigned_url = s3.generate_presigned_url(
    "put_object",
    Params={
        "Bucket": "my-bucket",
        "Key": "user-upload.png",
        "ContentType": "image/png",
    },
    ExpiresIn=3600,  # Valid for 1 hour
)

print(presigned_url)
# Return presigned_url to the client

有关预签名 URL 的完整文档,请参阅预签名 URL

有关所有支持的 S3 API 方法,请参阅 R2 的 S3 API 文档

通过 CLI 上传

Rclone

Rclone 是一个用于管理云存储文件的命令行工具。Rclone 非常适合从本地机器上传多个文件,或从其他云存储提供商复制数据。

要使用 rclone,请根据其官方文档将其安装到您的机器上 — 安装 rclone

使用 rclone copy 命令上传文件:

# Upload a single file
rclone copy /path/to/local/image.png r2:bucket_name

# Upload everything in a directory
rclone copy /path/to/local/folder r2:bucket_name

使用 rclone ls 验证上传:

rclone ls r2:bucket_name

有关更多信息,请参阅我们的 rclone 示例

Wrangler

使用 Wrangler 上传对象。运行 r2 object put 命令

wrangler r2 object put test-bucket/image.png --file=image.png

您可以通过可选标志设置 Content-Type(MIME 类型)、Content-DispositionCache-Control 和其他 HTTP 头元数据。

分片上传详情

分片大小限制

  • 最小分片大小:5 MiB(最后一个分片除外)
  • 最大分片大小:5 GiB
  • 最大分片数量:10,000
  • 除最后一个分片外,所有分片必须大小相同

未完成上传的生命周期

未完成的分片上传默认会在 7 天后自动中止。您可以通过配置自定义生命周期策略来更改此设置。

ETag

通过分片上传的对象的 ETag 与单次 PUT 上传的不同。每个分片的 ETag 是该分片内容的 MD5 哈希。已完成的分片对象的 ETag 是所有分片的二进制 MD5 值连接后的哈希,后跟连字符和分片数量。

例如,如果一个两分片上传的分片 ETag 为 bce6bf66aeb76c7040fdd5f4eccb78e68165449fc15bbf43d3b674595cbcc406,则已完成对象的 ETag 将为 f77dc0eecdebcd774a2a22cb393ad2ff-2

相关资源

Workers API 参考

R2 Workers API 完整参考,包括 put()、createMultipartUpload() 等。

预签名 URL

生成用于客户端访问的临时上传和下载 URL。

这篇文档对您有帮助吗?