我们在上传大文件的时候往往需要采用分片的方式,Amazon虽然提供了这种方式,但是不能实现我们后端在(宕机/重启)之后导致的文件丢失,大文件上传到一半可能会丢失数据的问题。
本文将利用本地线程池加数据库记录的方式,实现后端(宕机/重启)后恢复之前正在上传的文件,顺便利用SSE(Server Send Event)实现(宕机/重启)后进度条的还原。
首先引入maven,我用的是下面这个,目前最新版本1.12.731。
<dependency>
<groupId>com.amazonaws</groupId>
<artifactId>aws-java-sdk-s3</artifactId>
<version>1.12.731</version>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.16.1</version>
</dependency>
配置AmazonS3客户端
新建WebConfig,这里用BasicAWSCredentials创建基础的AWS认证,并在Spring中注入AmazonS3 的bean方便后续使用
// WebConfig.java
@Configuration
public class WebConfig {
private static final String accessKey = "你的accessKey";
private static final String secretKey = "你的secretKey";
@Bean
public AmazonS3 s3() {
BasicAWSCredentials awsCredentials = new BasicAWSCredentials(accessKey, secretKey);
return AmazonS3Client.builder()
.withRegion(Regions.CN_NORTHWEST_1)// 可以根据自己的需求进行选择
.withCredentials(new AWSStaticCredentialsProvider(awsCredentials))
.build();
}
@Bean
public ExecutorService executorService() {
return Executors.newFixedThreadPool(10);
}
@Bean
public ConcurrentMap<String, MyThread> uploadingFileMap() {
return new ConcurrentHashMap<>();
}
}
文件上传
让我们首先实现一个基础版本的分段文件上传
新建一个文件S3UploadController
// S3UploadController.java
private static final String bucketName = "testbuckname";
private final AmazonS3 s3;
private File curFile = null;
long filePosition = 0;
@PostMapping("/file")
public String file(MultipartFile file) throws IOException {
//重复上传直接忽略掉
if (curFile != null) {
return "等待其他文件上传完毕";
}
// 基于客户端认证创建TransferManager,它的主要功能是上传文件请求,等待上传完毕。
TransferManager tm = TransferManagerBuilder.standard().withS3Client(s3).build();
InitiateMultipartUploadRequest initRequest = new InitiateMultipartUploadRequest(bucketName, monthAndDayDir() + file.getOriginalFilename());
InitiateMultipartUploadResult initResponse = s3.initiateMultipartUpload(initRequest);
if (file.getOriginalFilename() == null) {
return "file is null";
}
// 文件上传位置(B)
filePosition = 0;
// 分片大小5MB
long partSize = 5 * 1024 * 1024;
int idx = file.getOriginalFilename().lastIndexOf(".");
// 创建临时文件,为什么要创建临时文件不用传入的file?,下面方法中只能用File类型,需要将MultipartFile转为File类型
curFile = File.createTempFile(file.getOriginalFilename().substring(0, idx), file.getOriginalFilename().substring(idx));
// 流传输,需要commons-io,要引入pom
FileUtils.copyInputStreamToFile(file.getInputStream(), curFile);
long contentLength = file.getSize();
// 监听
List<PartETag> partETags = new ArrayList<>();
// 这里需要开一个线程使用SSE 监听,下面会描述
MyThread myThread = new MyThread(tm, bucketName, curFile, 0, uploadingFileMap, initResponse.getUploadId(), links, service, partETags);
// 线程存到本地线程里面
uploadingFileMap.put(initResponse.getUploadId(), myThread);
// 运行
executorService.submit(myThread);
// 分段上传开始
for (int i = 1; filePosition < contentLength; i++) {
partSize = Math.min(partSize, (contentLength - filePosition));
UploadPartRequest uploadRequest = new UploadPartRequest()
&