大文件传输方案之分片上传AWS实战

引言

        随着云存储、视频分享、在线教育等领域的快速发展,用户上传大文件的需求变得越来越普遍。然而,大文件上传过程中常常面临网络波动、不稳定性以及客户端资源限制等问题,这些因素往往导致用户体验不佳。传统的整文件上传方式不仅容易因网络中断而导致上传失败,还可能占用大量内存和带宽资源,给用户设备和服务器带来沉重负担。

常见问题分析

1. 网络中断风险

        在上传大文件的过程中,网络不稳定或意外中断是一个常见问题。一旦网络中断,整个上传任务就会失败,用户不得不从头开始重新上传。这不仅浪费了用户的时间和带宽,还可能导致用户对服务的信任度下降。特别是在网络条件较差的地区,这种问题尤为突出。

2. 客户端资源消耗

        大文件上传会占用大量的内存和带宽资源,尤其是在低配置设备(如移动设备)上,容易导致设备卡顿或资源耗尽。这不仅影响上传任务的完成,还可能影响设备的其他正常操作。

3. 服务器负担

        如果没有有效的分片管理和断点续传机制,大文件上传失败后,服务器会存储许多不完整或重复的文件。这不仅浪费了存储资源,还增加了服务器的维护难度。

4. 用户体验不佳

        上传过程中缺乏实时反馈(如进度条),或者因为频繁失败而无法继续上传,会严重影响用户的满意度和使用意愿。用户在上传大文件时,往往希望能够实时了解上传进度,并在出现问题时能够快速恢复上传。

大文件分片上传

        大文件分片上传(Chunked Upload)是一种将大文件分割成多个小分片并分别上传的技术方案。这种方案能够有效解决大文件上传过程中遇到的网络中断、资源消耗和服务器负担等问题,同时提升上传的可靠性和效率。以下是分片上传的核心技术点及其实现细节:

1. 文件分片

        大文件分片上传的核心思想是将大文件切割成多个小的分片(Chunk),每个分片单独上传到服务器。这种方式不仅能够降低单次上传的数据量,还能在网络环境不稳定的情况下提高上传的成功率。

  • 优势

    • 提高可靠性:即使某个分片上传失败,只需重新上传该分片,而不需要重新上传整个文件。

    • 提升效率:分片上传可以充分利用网络带宽,多个分片可以并行上传,从而加快整体上传速度。

    • 降低资源占用:分片上传减少了单次上传的内存和带宽占用,特别适合低配置设备。

2. 分片的标识与管理

        为了确保分片上传的正确性和完整性,每个分片需要分配一个唯一标识,以便前后端协作追踪上传状态和重建文件。

  • 唯一标识

    • 每个分片可以分配一个索引(如分片序号)或哈希值(如MD5或SHA-1),用于标识分片的唯一性。

    • 前端在上传分片时,需要将分片的标识信息(如分片序号、文件总大小、分片大小等)一并发送到服务器。

  • 状态管理

    • 服务器需要记录每个分片的上传状态(如已上传、未上传、上传失败等),以便在断点续传时快速定位需要重新上传的分片。

    • 前端可以通过接口查询上传进度,实时更新用户界面。

3. 分片大小的选择策略

        分片大小的选择是分片上传中的一个关键问题。分片大小需要综合考虑网络条件、文件大小和服务器性能等因素。

  • 影响因素

    • 网络条件:在网络环境较差的情况下,较小的分片可以提高上传的成功率,但会增加请求次数;在网络环境较好的情况下,较大的分片可以减少请求次数,提高上传效率。

    • 文件大小:对于超大文件,较小的分片可以降低单次上传的风险;对于较小的文件,较大的分片可以减少分片数量。

    • 服务器性能:分片过小会增加服务器的请求处理压力,分片过大可能会占用过多的服务器资源。

  • 推荐策略

    • 通常,分片大小在几MB到几十MB之间较为合适。例如,常见的分片大小为5MB、10MB或20MB。

    • 可以根据实际场景动态调整分片大小,例如在网络环境较差时自动减小分片大小。

4. 分片文件合并

        当所有分片上传完成后,服务器需要将这些分片合并成完整的文件。这是分片上传的最后一步,也是确保文件完整性的关键环节。

  • 临时存储

    • 分片上传到服务器后,会存储在临时目录中,等待全部分片上传完成。

    • 服务器需要为每个上传任务分配一个唯一的标识(如任务ID),以便将分片与对应的上传任务关联。

  • 合并操作

    • 当所有分片上传完成后,服务器会按照分片的顺序将它们合并成完整的文件。

    • 合并过程中需要检查每个分片的完整性和正确性,确保最终文件的完整性。

  • 清理机制

    • 合并完成后,服务器需要清理临时存储的分片文件,释放存储空间。

    • 对于未完成的上传任务,可以设置超时机制,定期清理无效的分片文件。

前后端交互逻辑

        前端、后端和文件服务器(如MinIO)之间的交互逻辑至关重要。我们将一起思考一下交互实现方案。

方案一:前端 → 后端 → MinIO

        在这种方案中,前端将文件上传到后端服务器,后端服务器再将文件传输到MinIO。这种方案的核心思想是通过后端服务器集中管理文件上传过程。

优点

  • 集中管理:所有文件上传操作都通过后端进行集中管理,可以方便地实现身份验证、权限控制、文件处理等业务逻辑。例如,可以在文件上传到MinIO之前生成缩略图或提取元数据。

  • 安全性:后端可以作为中间层,对上传的文件进行安全检查和过滤,防止恶意文件上传到MinIO。例如,后端可以对文件进行病毒扫描、文件类型校验等操作,确保上传的文件安全可靠。

  • 业务逻辑扩展:在文件上传过程中,后端可以加入更多的业务逻辑,如文件内容的预处理、格式转换等。例如,上传的视频文件可以在后端进行转码,以适应不同的播放设备。

缺点

  • 性能压力:后端服务器需要处理文件的中转,增加了服务器的负载和响应时间。特别是对于大文件,后端需要先接收文件,再将其传输到MinIO,这会显著增加延迟。在高并发场景下,这种延迟可能会成为系统的瓶颈。

  • 单点故障:如果后端服务器出现故障,整个文件上传流程将受到影响,即使MinIO服务正常运行,也无法完成文件上传。这种单点故障的风险在分布式系统中尤为突出。

  • 资源占用:后端服务器需要额外的资源来处理文件流,尤其是在高并发场景下,可能会导致服务器资源紧张,影响其他业务逻辑的处理。

方案二:前端直接传输到MinIO

        在这种方案中,前端直接上传文件到MinIO,绕过后端服务器的中转。这种方案的核心思想是减少后端服务器的负载,提高文件上传的效率。

优点

  • 减少延迟:前端直接将文件上传到MinIO,减少了文件在后端的中转时间,显著提高了上传效率。特别是在大文件上传的场景下,这种方案可以大大缩短上传时间。

  • 减轻后端压力:后端服务器不需要处理文件的中转,可以专注于业务逻辑处理,减少了服务器的负载。这种方案特别适合高并发的场景,能够有效提升系统的整体性能。

  • 高可用性:即使后端服务器出现故障,只要MinIO服务正常运行,文件上传仍然可以继续进行,提高了系统的高可用性。这种设计增强了系统的容错能力。

缺点

  • 安全性:前端直接上传文件到MinIO,需要生成并返回一个有时效的上传凭证,增加了安全风险。需要确保上传凭证的安全性,防止被恶意利用。例如,上传凭证可能会被截获或伪造,导致未经授权的文件上传。

  • 权限控制:虽然可以通过上传凭证实现一定程度的权限控制,但相比后端集中管理,权限控制的灵活性和安全性可能会稍弱。例如,后端可以基于用户的角色和权限动态生成上传凭证,而前端直接上传的方案则难以实现这种动态控制。

  • 业务逻辑限制:前端直接上传文件到MinIO,后端无法在文件上传过程中加入复杂的业务逻辑,如文件内容的预处理、格式转换等。这种限制可能会影响系统的功能扩展性。

原生API与AWS SDK的深度对比

        在大文件上传的场景中,如何高效、稳定地处理文件上传是一个常见的挑战。AWS S3 提供了两种主要的上传方案:原生API和AWS SDK。本文将从基本原理、前端实现、后端实现以及优缺点等方面,深入探讨这两种方案的差异,并分析其适用场景。

基本原理

文件上传的基本流程

        文件上传的核心是将客户端的文件通过HTTP协议传输到服务器端,并在服务器端进行存储。对于小文件,直接上传是可行的,但对于大文件,直接上传可能会遇到网络不稳定、超时等问题。

分片上传的原理

        分片上传(Multipart Upload)是一种将大文件拆分成多个小块(chunk)的技术。每个小块独立上传,最后在服务器端将这些小块合并成完整的文件。这种方式可以有效解决大文件上传的稳定性问题,同时支持断点续传。

前端实现

文件读取与分块

        前端可以使用HTML5的File APIBlob对象来读取文件并将其分割成小块。File API提供了FileReaderBlob.slice()方法,能够方便地将大文件切割成多个小块。

const file = document.getElementById('fileInput').files[0];
const chunkSize = 5 * 1024 * 1024; // 每个分块5MB
let offset = 0;

while (offset < file.size) {
    const chunk = file.slice(offset, offset + chunkSize);
    uploadChunk(chunk);
    offset += chunkSize;
}

分块上传

        分块上传可以通过AJAX技术将每个小块发送到后端。AJAX支持异步上传,能够在不刷新页面的情况下上传文件。此外,也可以使用Fetch API或axios等现代前端库来实现。

function uploadChunk(chunk) {
    const formData = new FormData();
    formData.append('file', chunk);
    axios.post('/upload', formData)
        .then(response => console.log('Chunk uploaded'))
        .catch(error => console.error('Upload failed', error));
}

后端实现:原生Java与AWS SDK对比

原生Java实现

        使用Java原生API实现大文件上传,通常依赖于javax.servletjavax.servlet.http包中的类来处理文件上传。

  • 实现步骤

    • 通过HttpServletRequest对象获取文件项。

    • 使用getInputStream()方法获取输入流,将文件内容写入服务器上的指定位置。

    • 手动处理文件的分块、传输和合并。

  • 优点

    • 灵活性高:可以根据具体需求进行定制,例如实现断点续传、自定义分块大小、文件的读取、分块、传输和合并。

    • 可控性强:开发者可以完全控制上传流程,适合对上传过程有特殊需求的场景。

  • 缺点

    • 复杂度高:需要手动处理文件的分块、传输和合并,代码复杂度较高,容易出错。

    • 维护成本高:随着业务需求的变化,代码的维护和扩展成本较高。

AWS SDK实现

        AWS提供了aws-java-sdk-s3库,封装了丰富的API,简化了大文件上传的开发过程。

  • 实现步骤

    • 初始化分片上传任务。

    • 查询已经传输的分片。

    • 获取临时上传的预签名地址。

    • 合并分片文件。

  • 优点

    • 开发效率高:SDK提供了丰富的API,开发者无需关心底层的分片、传输和合并逻辑,直接调用API即可。

    • 兼容性和稳定性好:AWS S3 是一个成熟的云存储服务,具有良好的兼容性和稳定性,适合大规模文件上传场景。

    • 支持断点续传:AWS SDK 内置了断点续传的功能,上传过程中断后可以从断点处继续上传。

  • 缺点

    • 学习曲线:需要学习AWS S3的API和相关概念,有一定的学习成本。

    • 依赖AWS服务:使用AWS SDK意味着必须依赖AWS的基础设施,可能不适合所有场景。

AWS-S3分片上传技术详解

    AWS S3作为业界领先的对象存储服务,提供了强大的分片上传(Multipart Upload)功能。通过合理利用 S3 的 API 和特性,开发者可以显著降低分片上传流程的编程复杂度,同时提升系统的可靠性和性能。

第一步:测试初始化分片任务上传,获取 uploadId

初始化分片任务

        在分片上传的流程中,首先需要初始化一个分片上传任务。这个步骤的核心是生成一个唯一的 uploadId,用于标识本次分片上传任务。uploadId 是后续所有操作的关键标识符,确保每个分片都能正确关联到同一个上传任务。

断点续传的处理

        如果在初始化时发现已经存在 uploadId,则说明这是一个断点续传的场景。此时,系统不应重新生成 uploadId,而是复用已有的 uploadId,以确保上传任务的一致性。断点续传的实现依赖于服务端对已上传分片的记录,客户端可以通过查询接口获取已上传的分片信息,从而避免重复上传。

为什么 uploadId 是分片上传的核心标识?

  uploadId 是服务端用来追踪分片上传任务的唯一标识。它不仅用于分片的上传,还用于分片的合并和清理操作。如果没有 uploadId,服务端将无法正确管理分片任务,导致上传失败或数据不一致。

如何确保 uploadId 的唯一性和安全性?

  uploadId 通常由服务端生成,可以使用 UUID 或其他唯一性算法来保证其唯一性。同时,uploadId 应通过加密传输,避免被恶意篡改。

/**
	 * 第一步:初始化大文件分片上传任务,获取uploadId
	 * 如果初始化时有 uploadId,说明是断点续传,不能重新生成 uploadId
	 */
	@Test
	public void testInitiateMultipartUploadTask() {
		String bucketName = "test";
		String objectKey = "/aa/bb/cc/666.txt";

		ObjectMetadata objectMetadata = new ObjectMetadata();
		objectMetadata.setContentType("text/plain");

		//初始化分片上传请求
		InitiateMultipartUploadRequest initRequest =
				new InitiateMultipartUploadRequest(bucketName, objectKey, objectMetadata);

		//初始化分片上传任务
		InitiateMultipartUploadResult uploadResult = amazonS3Client.initiateMultipartUpload(initRequest);
		String uploadId = uploadResult.getUploadId();
		log.info("uploadId:{}",uploadId);

	}

第二步:测试初始化并生成多个预签名 URL,返回给前端

生成预签名 URL

        在分片上传的过程中,客户端需要将每个分片上传到存储服务(如 MinIO)。为了确保上传的安全性,服务端通常会为每个分片生成一个预签名 URL(Pre-signed URL)。预签名 URL 是一种临时授权的访问链接,允许客户端在有限的时间内将分片上传到指定的存储位置。

返回临时 MinIO 地址

        服务端生成预签名 URL 后,将其返回给前端,前端可以直接使用这些 URL 将分片上传到 MinIO,而无需额外的身份验证。这种方式不仅简化了客户端的逻辑,还提高了上传的安全性,因为预签名 URL 的有效期通常较短,避免了 URL 被滥用的风险。

预签名 URL 的生成机制是什么?

       预签名 URL 是基于存储服务的访问密钥(Access Key)和签名算法生成的。服务端通过计算分片的上传路径、有效期等参数,生成一个带有签名的 URL。客户端使用该 URL 可以直接上传数据,而无需暴露存储服务的访问密钥。

 如何优化预签名 URL 的生成和分发?

        对于大规模分片上传场景,预签名 URL 的生成和分发可能成为性能瓶颈。可以通过批量生成 URL、缓存 URL 或使用异步生成机制来优化性能。

    /**
	 * 第二步:测试初始化并生成多个预签名URL,返回给前端
	 */
	@Test
	public void testGenePreSignedUrls() {

		String bucketName = "test";
		String objectKey = "/aa/bb/cc/666.txt";
		//分片数量,这里配置4个
		int chunkCount = 4;
		String uploadId = "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX";

		//存储预签名的地址
		List<String> preSignedUrls = new ArrayList<>(chunkCount);
		//遍历每个分片,生成预签名地址
		for (int i = 1; i <= chunkCount; i++) {
			//生成预签名URL,配置过期时间,1小时的时间
			Date expireDate = DateUtil.offsetMillisecond(new Date(), 3600 * 1000);
			//创建生成签名URL的请求,并且指定方法为PUT
			GeneratePresignedUrlRequest request = new GeneratePresignedUrlRequest(bucketName, objectKey)
					.withExpiration(expireDate).withMethod(HttpMethod.PUT);

			//添加上传ID,和分片编号做为请求参数
			request.addRequestParameter("uploadId", uploadId);
			request.addRequestParameter("partNumber", String.valueOf(i));
			//请求签名URL
			URL url = amazonS3Client.generatePresignedUrl(request);
			preSignedUrls.add(url.toString());
			log.info("preSignedUrl:{}",url);
		}


	}

第三步:合并分片

检查分片完整性

        在所有分片上传完成后,服务端需要根据 uploadId 标识的分片上传任务,检查已上传的分片数量是否与预期一致。如果分片数量不一致,说明上传过程中可能存在分片丢失或上传失败的情况,此时应通知客户端重新上传缺失的分片。

合并分片

        当所有分片都上传完成后,服务端需要将这些分片合并成一个完整的文件。合并操作通常由存储服务(如 MinIO)提供,服务端只需调用相应的 API,并传入 uploadId 和分片列表即可。合并完成后,存储服务会生成一个最终的文件,并返回其访问地址。

分片合并的性能瓶颈是什么?

        分片合并操作通常涉及大量的 I/O 操作,尤其是在分片数量较多或文件较大的情况下。为了优化性能,可以采用并行合并、分片预排序等策略。

如何处理分片合并失败的情况?

        分片合并可能因网络故障、存储服务异常等原因失败。此时,服务端应记录合并失败的状态,并提供重试机制,确保最终能够成功合并。

​
    /**
	 * 合并分片
	 */
	@Test
	public void testMergeChunk() {

		String bucketName = "test";
		String objectKey = "/aa/bb/cc/666.txt";
		//分片数量,这里配置4个
		int chunkCount = 4;
		String uploadId = "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX";

		//创建一个列出分片请求
		ListPartsRequest listPartsRequest = new ListPartsRequest(bucketName, objectKey, uploadId);
		PartListing partListing = amazonS3Client.listParts(listPartsRequest);
		List<PartSummary> partList = partListing.getParts();

		//检查分片数量和预期的是否一致
		if (partList.size() != chunkCount) {
			//已经上传的分片数量和记录中的不一样,不能合并
			throw new RuntimeException("分片数量不一致");
		}

		//创建完成分片上传请求对象,进行合并
		CompleteMultipartUploadRequest completeMultipartUploadRequest =
				new CompleteMultipartUploadRequest()
						.withBucketName(bucketName)
						.withKey(objectKey)
						.withUploadId(uploadId)
						.withPartETags(
								partList.stream()
										.map(partSummary ->
												new PartETag(partSummary.getPartNumber(), partSummary.getETag()))
										.collect(Collectors.toList()));

		//完成分片上传合并,获取结果
		CompleteMultipartUploadResult result = amazonS3Client.completeMultipartUpload(completeMultipartUploadRequest);
		log.info("result:{}",result.getLocation());


	}

​

其他步骤:获取已上传的分片文件及上传进度

获取已上传的分片文件
        在上传过程中,客户端可能需要查询已上传的分片文件,以便实现断点续传或上传进度的展示。服务端提供接口,允许客户端根据 uploadId 查询已上传的分片列表。

获取上传进度
        上传进度的计算通常基于已上传的分片数量和总分片数量。服务端可以根据 uploadId 统计已上传的分片数量,并返回给前端。前端可以根据这些信息实时更新上传进度。

如何确保上传进度计算的准确性?

        上传进度的计算依赖于服务端对已上传分片的准确记录。为了避免误差,服务端应在每次分片上传成功后立即更新分片状态,并提供事务性保证,确保数据的一致性。

如何处理分片上传的并发问题?

        在多客户端并发上传的场景下,可能会出现分片覆盖或丢失的情况。服务端应通过分布式锁或乐观锁机制,确保每个分片的上传操作是原子性的。

​
    /**
	 * 其他步骤:上传进度验证,获取已经上传的分片文件, 未上传完成,调用接口获取上传进度
	 */
	@Test
	public void testGetUploadProgress() {
		String bucketName = "test";
		String objectKey = "/aa/bb/cc/666.txt";
		//分片数量,这里配置4个
		int chunkCount = 4;
		String uploadId = "NzVhZjVjY2YtNzBhNS00YWE0LThjYjQtZmQzNmFkMTQyNTRmLmY1OGY2ZTEyLTY5YjYtNDQ3ZC04ZWMxLWJlZTVmOGFmZTkzYw";

		//检查对应的桶里面是否存在对应的对象
		boolean doesObjectExist = amazonS3Client.doesObjectExist(bucketName, objectKey);
		if(!doesObjectExist){
			//未上传完成,返回已经上传的分片文件
			ListPartsRequest listPartsRequest = new ListPartsRequest(bucketName, objectKey, uploadId);
			PartListing partListing = amazonS3Client.listParts(listPartsRequest);
			List<PartSummary> partList = partListing.getParts();

			//创建一个结果,用于存储上传状态和分片列表
			Map<String,Object> result = new HashMap<>(2);
			result.put("finished",false);
			result.put("exitPartList",partList);

			//前端就可以通过这个判断是否要调用merge合并接口
			log.info("result:{}",result);

			//遍历每个分片的信息
			for (PartSummary partSummary : partList) {
				System.out.println("getPartNumber:" + partSummary.getPartNumber() +
						",getETag=" + partSummary.getETag() + ",getSize= " + partSummary.getSize() +
						",getLastModified=" + partSummary.getLastModified());
			}
		}
	}

​

封装类

接口

import com.amazonaws.HttpMethod;
import com.amazonaws.services.s3.model.*;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.web.multipart.MultipartFile;

import java.net.URL;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;

public interface StoreEngine {
    //==================大文件分片上传相关接口=========================

    /**
     * 查询分片数据
     * @param bucketName 存储桶名称
     * @param objectKey 对象名称
     * @param uploadId 分片上传ID
     * @return 分片列表对象
     */
    PartListing listMultipart(String bucketName, String objectKey, String uploadId);

    /**
     * 1-初始化分片上传任务,获取uploadId,如果初始化时有 uploadId,说明是断点续传,不能重新生成 uploadId
     * @param bucketName 存储桶名称
     * @param objectKey 对象名称
     * @param metadata 对象元数据
     * @return 初始化分片上传结果对象,包含uploadId等信息
     */
    InitiateMultipartUploadResult initMultipartUploadTask(String bucketName, String objectKey, ObjectMetadata metadata);


    /**
     * 2-生成分片上传地址,返回给前端
     * @param bucketName 存储桶名称
     * @param objectKey 对象名称
     * @param httpMethod HTTP方法,如GET、PUT等
     * @param expiration 签名过期时间
     * @param params 签名中包含的参数
     * @return 生成的预签名URL
     */
    URL genePreSignedUrl(String bucketName, String objectKey, HttpMethod httpMethod, Date expiration, Map<String,Object> params);

    /**
     * 3-合并分片
     * @param bucketName 存储桶名称
     * @param objectKey 对象名称
     * @param uploadId 分片上传ID
     * @param partETags 分片ETag列表,用于验证分片的完整性
     * @return 完成分片上传结果对象
     */
    CompleteMultipartUploadResult mergeChunks(String bucketName, String objectKey, String uploadId, List<PartETag> partETags);


}

实现类


import com.amazonaws.HttpMethod;
import com.amazonaws.services.s3.AmazonS3Client;
import com.amazonaws.services.s3.model.*;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.apache.tomcat.util.http.fileupload.IOUtils;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;

import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;

@Component
@Slf4j
public class MinIOFileStoreEngine implements StoreEngine {
    @Override
    public PartListing listMultipart(String bucketName, String objectKey, String uploadId) {
        try {
            ListPartsRequest request = new ListPartsRequest(bucketName, objectKey, uploadId);
            return amazonS3Client.listParts(request);
        } catch (Exception e) {
            log.error("errorMsg={}", e);
            return null;
        }
    }

    @Override
    public InitiateMultipartUploadResult initMultipartUploadTask(String bucketName, String objectKey, ObjectMetadata metadata) {
        try {
            InitiateMultipartUploadRequest request = new InitiateMultipartUploadRequest(bucketName, objectKey, metadata);
            return amazonS3Client.initiateMultipartUpload(request);
        } catch (Exception e) {
            log.error("errorMsg={}", e);
            return null;
        }
    }


    @Override
    public URL genePreSignedUrl(String bucketName, String objectKey, HttpMethod httpMethod, Date expiration, Map<String, Object> params) {
        try {
            GeneratePresignedUrlRequest genePreSignedUrlReq =
                    new GeneratePresignedUrlRequest(bucketName, objectKey, httpMethod)
                            .withExpiration(expiration);
            //遍历params作为参数加到genePreSignedUrlReq里面,比如 添加上传ID和分片编号作为请求参数
            //genePreSignedUrlReq.addRequestParameter("uploadId", uploadId);
            //genePreSignedUrlReq.addRequestParameter("partNumber", String.valueOf(i));
            for (Map.Entry<String, Object> entry : params.entrySet()) {
                genePreSignedUrlReq.addRequestParameter(entry.getKey(), String.valueOf(entry.getValue()));
            }
            // 生成并获取预签名URL
            return amazonS3Client.generatePresignedUrl(genePreSignedUrlReq);
        } catch (Exception e) {
            log.error("errorMsg={}", e);
            return null;
        }
    }

    @Override
    public CompleteMultipartUploadResult mergeChunks(String bucketName, String objectKey, String uploadId, List<PartETag> partETags) {
        CompleteMultipartUploadRequest request = new CompleteMultipartUploadRequest(bucketName, objectKey, uploadId, partETags);
        return amazonS3Client.completeMultipartUpload(request);

    }
}

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

敲键盘的小夜猫

你的鼓励就是我创作的最大动力。

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值