You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

.NET中借助Presigned URL分片上传大文件至AWS S3 Bucket方法咨询

基于Presigned URL的S3大文件分片上传实现方案

下面是结合S3 Multipart Upload和Presigned URL实现大文件分片上传的完整技术方案,包含后端(Python)和前端(JavaScript)的具体实现步骤及代码示例:

一、核心流程概述

S3 Multipart Upload分为三个核心阶段,每个阶段依赖后端生成对应的Presigned URL(或直接调用AWS API):

  1. 初始化上传:在S3中创建一个Multipart Upload会话,获取唯一的UploadId。
  2. 分片上传:将本地大文件切割为多个分片,使用每个分片对应的Presigned URL单独上传,收集每个分片的ETag。
  3. 完成上传:将所有分片的ETag和分片编号提交给S3,合并所有分片为完整文件。

二、后端实现(Python + Boto3)

后端负责与S3交互生成必要的Presigned URL,以及处理上传的初始化与完成操作。

1. 初始化Multipart Upload

调用S3 API创建上传会话,返回UploadId供后续步骤使用:

import boto3
from botocore.exceptions import ClientError

# 初始化S3客户端(建议通过环境变量配置凭证,避免硬编码)
s3_client = boto3.client('s3')

def initiate_multipart_upload(bucket_name: str, object_key: str) -> str:
    try:
        response = s3_client.create_multipart_upload(
            Bucket=bucket_name,
            Key=object_key
        )
        return response['UploadId']
    except ClientError as e:
        raise RuntimeError(f"初始化Multipart上传失败: {e.response['Error']['Message']}") from e

2. 生成分片上传的Presigned URL

为每个分片生成带UploadId和PartNumber的Presigned URL,前端通过该URL直接上传分片到S3:

def generate_presigned_part_url(bucket_name: str, object_key: str, upload_id: str, part_number: int) -> str:
    try:
        return s3_client.generate_presigned_url(
            'upload_part',
            Params={
                'Bucket': bucket_name,
                'Key': object_key,
                'UploadId': upload_id,
                'PartNumber': part_number
            },
            ExpiresIn=3600  # URL有效期1小时,可根据上传时长调整
        )
    except ClientError as e:
        raise RuntimeError(f"生成分片预签名URL失败: {e.response['Error']['Message']}") from e

3. 完成Multipart Upload

收集所有分片的ETag后,调用S3 API合并分片为完整文件:

def complete_multipart_upload(
    bucket_name: str,
    object_key: str,
    upload_id: str,
    parts: list[dict]
) -> dict:
    # parts格式: [{'PartNumber': 1, 'ETag': 'xxx'}, ...]
    try:
        return s3_client.complete_multipart_upload(
            Bucket=bucket_name,
            Key=object_key,
            UploadId=upload_id,
            MultipartUpload={'Parts': parts}
        )
    except ClientError as e:
        raise RuntimeError(f"完成Multipart上传失败: {e.response['Error']['Message']}") from e

4. 可选:中断上传清理临时分片

如果上传中途失败,调用此方法清理S3上的临时分片,避免占用存储:

def abort_multipart_upload(bucket_name: str, object_key: str, upload_id: str) -> None:
    try:
        s3_client.abort_multipart_upload(
            Bucket=bucket_name,
            Key=object_key,
            UploadId=upload_id
        )
    except ClientError as e:
        print(f"清理临时分片失败: {e.response['Error']['Message']}")

三、前端实现(浏览器JavaScript)

前端负责切割本地文件、调用Presigned URL上传分片,并收集ETag提交给后端完成上传。

1. 文件分片切割

将大文件按指定大小切割为多个Blob对象:

// 分片大小设置为5MB(S3要求分片最小5MB,最后一片除外)
const CHUNK_SIZE = 5 * 1024 * 1024;

function splitFileIntoChunks(file) {
    const chunks = [];
    let startOffset = 0;
    while (startOffset < file.size) {
        const endOffset = Math.min(startOffset + CHUNK_SIZE, file.size);
        chunks.push(file.slice(startOffset, endOffset));
        startOffset = endOffset;
    }
    return chunks;
}

2. 分片上传与ETag收集

逐个上传分片,从响应头中提取ETag(注意去除引号):

async function uploadChunks(chunks, presignedUrls) {
    const partETags = [];
    
    for (let index = 0; index < chunks.length; index++) {
        const chunk = chunks[index];
        const url = presignedUrls[index];
        const partNumber = index + 1;
        
        try {
            const response = await fetch(url, {
                method: 'PUT',
                body: chunk,
                headers: {
                    'Content-Type': chunk.type || 'application/octet-stream'
                }
            });
            
            if (!response.ok) {
                throw new Error(`分片 ${partNumber} 上传失败,状态码: ${response.status}`);
            }
            
            // 提取ETag,去除前后引号
            const etag = response.headers.get('ETag').replace(/"/g, '');
            partETags.push({ PartNumber: partNumber, ETag: etag });
        } catch (error) {
            console.error(`分片 ${partNumber} 上传出错:`, error);
            // 可添加重试逻辑,例如最多重试3次
            throw error;
        }
    }
    
    return partETags;
}

3. 完整前端上传流程

整合所有步骤,与后端接口交互完成整个上传流程:

async function uploadLargeFile(file, bucketObjectKey) {
    try {
        // 1. 调用后端接口初始化上传,获取UploadId
        const initResp = await fetch('/api/s3/init-multipart', {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({ key: bucketObjectKey })
        });
        const { uploadId } = await initResp.json();
        
        // 2. 切割文件为分片
        const chunks = splitFileIntoChunks(file);
        
        // 3. 调用后端接口获取所有分片的Presigned URL
        const urlsResp = await fetch('/api/s3/get-part-urls', {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({
                key: bucketObjectKey,
                uploadId,
                partCount: chunks.length
            })
        });
        const presignedUrls = await urlsResp.json();
        
        // 4. 上传所有分片并收集ETag
        const partETags = await uploadChunks(chunks, presignedUrls);
        
        // 5. 调用后端接口完成上传
        const completeResp = await fetch('/api/s3/complete-multipart', {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({
                key: bucketObjectKey,
                uploadId,
                parts: partETags
            })
        });
        
        if (completeResp.ok) {
            console.log('文件上传成功!');
            return await completeResp.json();
        } else {
            throw new Error('完成上传请求失败');
        }
    } catch (error) {
        console.error('上传流程出错:', error);
        // 可调用后端接口中断上传,清理临时分片
        // await fetch('/api/s3/abort-multipart', { ... });
        throw error;
    }
}

四、关键注意事项

  • 分片大小限制:S3要求除最后一片外,所有分片大小至少为5MB,最大单分片为5GB;建议根据网络带宽调整,比如5MB-1GB之间。
  • Presigned URL有效期:需设置足够覆盖所有分片上传的时间,避免中途URL失效;如果上传时间较长,可考虑动态生成后续分片的URL。
  • 断点续传:如需支持断点续传,前端可本地存储已上传分片的PartNumber和ETag,下次上传时先调用后端的list_parts接口查询S3上已有的分片,仅上传未完成的部分。
  • 重试机制:针对网络波动导致的上传失败,建议给分片上传添加重试逻辑(如最多3次重试),重试时可复用同一个Presigned URL。
  • 权限控制:确保生成Presigned URL的IAM用户拥有s3:CreateMultipartUpload、s3:UploadPart、s3:CompleteMultipartUpload、s3:AbortMultipartUpload权限。

内容的提问来源于stack exchange,提问作者Debashis D

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.17 04:57:07