阿里云OSS多线程分片上传
1、首先开通阿里云OSS存储,这里不多说了
2、创建一个Bucket
按照你自己的需求选择
3、创建好之后,点击Access Key,来获取accessKeyId、accessKeySecret这两个参数
4. Maven依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!--aliyun-oss-->
<dependency>
<groupId>com.aliyun.oss</groupId>
<artifactId>aliyun-sdk-oss</artifactId>
<version>3.8.0</version>
</dependency>
<!--commons-fileupload-->
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.3.1</version>
</dependency>
<!--lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.8</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
5、YML
#阿里云 OSS
aliyun:
oss:
file:
endpoint: ********* #Endpoint(地域节点)
accessKeyId: ********** #你自己的accessKeyId
accessKeySecret: ********* #你自己的AccessKeySecret
bucketName: ***** #Bucket存储空间
url: ******* #Bucket 域名
6、新建一个AliyunOSS.java 实体类
@Data
@Component
@ConfigurationProperties("aliyun.oss.file")
public class AliyunOSS {
private String endpoint; //Endpoint(地域节点)
private String accessKeyId; //accessKeyId
private String accessKeySecret; //AccessKeySecret
private String bucketName; //Bucket存储空间
private String url; //Bucket 域名
private String[] fileHost;
}
7.新建一个AliyunOSSServiceImpl.java
package cc.flysoft.m.service.impl;
import cc.flysoft.m.entity.AliyunOSS;
import cc.flysoft.m.service.AliyunOSSService;
import cc.flysoft.m.util.FileName;
import com.aliyun.oss.*;
import com.aliyun.oss.model.*;
import org.apache.commons.lang.StringUtils;
import org.apache.tomcat.util.threads.ThreadPoolExecutor;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import java.io.*;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
/**
* @author zhanglin
* @descibe AliyunOSS
* @date 2022/6/03 15:06
*/
@Service
public class AliyunOSSServiceImpl implements AliyunOSSService {
private static final org.slf4j.Logger logger = LoggerFactory.getLogger(AliyunOSSServiceImpl.class);
@Autowired
private AliyunOSS aliyunOSS;
/**
* 普通文件上传
*/
@Override
public String upLoad(MultipartFile file) {
logger.info("\n<<------OSS文件上传开始-------->>\n"+file.getOriginalFilename());
// 判断文件
if (file == null) {
return null;
}
OSSClient client = new OSSClient(aliyunOSS.getEndpoint(), aliyunOSS.getAccessKeyId(),aliyunOSS.getAccessKeySecret());
try {
// 判断容器是否存在,不存在就创建
if (!client.doesBucketExist(aliyunOSS.getBucketName())) {
client.createBucket(aliyunOSS.getBucketName());
CreateBucketRequest createBucketRequest = new CreateBucketRequest(aliyunOSS.getBucketName());
createBucketRequest.setCannedACL(CannedAccessControlList.PublicRead);
client.createBucket(createBucketRequest);
}
//文件重命名
String fileUrl = this.getFileName(file);
// 上传文件
PutObjectResult result = client.putObject(new PutObjectRequest(aliyunOSS.getBucketName(), fileUrl, file.getInputStream()));
// 设置权限(公开读)
client.setBucketAcl(aliyunOSS.getBucketName(), CannedAccessControlList.PublicRead);
if (result != null) {
logger.info("\n<<------OSS文件上传成功------>>\n "+aliyunOSS.getUrl() + fileUrl);
return aliyunOSS.getUrl()+ fileUrl;
}
} catch (OSSException oe) {
logger.error(oe.getMessage());
} catch (ClientException ce) {
logger.error(ce.getErrorMessage());
} catch (IOException e) {
e.printStackTrace();
logger.error(e.getMessage());
} finally {
if (client != null) {
client.shutdown();
}
}
return null;
}
/**
* 多线程,大文件分片上传
* @param file
* @return
*/
@Override
public String fragmentUpload(MultipartFile file){
long startTime = System.currentTimeMillis();
//构造一个线程池
ThreadPoolExecutor executor = new ThreadPoolExecutor(12,
20,
60,
TimeUnit.SECONDS,
new LinkedBlockingQueue<>(200));
// 创建OSSClient实例。
OSS ossClient = new OSSClientBuilder().build(aliyunOSS.getEndpoint(), aliyunOSS.getAccessKeyId(), aliyunOSS.getAccessKeySecret());
//设置为公共读,私有写
ossClient.setBucketAcl(aliyunOSS.getBucketName(), CannedAccessControlList.PublicRead);
//重命名后文件名
String objectName = getFileName(file);
try {
// 创建OSSClient实例。
InitiateMultipartUploadRequest request = new InitiateMultipartUploadRequest(aliyunOSS.getBucketName(), objectName);
// 初始化分片。
InitiateMultipartUploadResult upresult = ossClient.initiateMultipartUpload(request);
// 返回uploadId,它是分片上传事件的唯一标识,您可以根据这个uploadId发起相关的操作,如取消分片上传、查询分片上传等。
String uploadId = upresult.getUploadId();
// partETags是PartETag的集合。PartETag由分片的ETag和分片号组成。
List<PartETag> partETags = new ArrayList<PartETag>();
// 设置每块为 1M
final long partSize = 1 * 1024 * 1024L;
// 转换文件类型
long fileLength = file.getSize();
logger.info("文件大小:"+fileLength);
int partCount = (int) (fileLength / partSize);
if (fileLength % partSize != 0) {
partCount++;
}
// 分块 号码的范围是1~10000。如果超出这个范围,OSS将返回InvalidArgument的错误码。
if (partCount > 10000) {
throw new RuntimeException("文件过大(分块大小不能超过10000)");
} else {
logger.info("一共分了 " + partCount + " 块");
}
// 遍历分片上传。
for (int i = 0; i < partCount; i++) {
executor.execute(new PartUploader(i,partSize,partCount,file,fileLength,aliyunOSS,objectName,uploadId,ossClient,partETags));
}
// 等待所有分片完毕,关闭线程池(线程池不马上关闭)
// 执行以前提交的任务,但不接受新任务。
executor.shutdown();
// 如果关闭后所有任务都已完成,则返回 true。
while (!executor.isTerminated()) {
try {
// 用于等待子线程结束,再继续执行下面的代码
executor.awaitTermination(5, TimeUnit.SECONDS);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//验证是否所有的分片都完成
if(partETags.size()!=partCount){
throw new IllegalStateException("文件的某些部分上传失败!");
}else {
logger.info("成功上传文件:{}",file.getOriginalFilename());
}
/**完成分片上传**/
//排序。partETags必须按分片号升序排列
Collections.sort(partETags, new Comparator<PartETag>() {
@Override
public int compare(PartETag o1, PartETag o2) {
return o1.getPartNumber()-o2.getPartNumber();
}
});
// 创建CompleteMultipartUploadRequest对象。
// 在执行完成分片上传操作时,需要提供所有有效的partETags。
// OSS收到提交的partETags后,会逐一验证每个分片的有效性。
// 当所有的数据分片验证通过后,OSS将把这些分片组合成一个完整的文件。
CompleteMultipartUploadRequest completeUpload = new CompleteMultipartUploadRequest(aliyunOSS.getBucketName(), objectName, uploadId, partETags);
// 完成上传。
ossClient.completeMultipartUpload(completeUpload);
} catch (Exception e) {
logger.error(e.getMessage());
return "上传文件异常!";
} finally {
if (ossClient!=null){
// 关闭OSSClient。
ossClient.shutdown();
}
}
long endTime = System.currentTimeMillis();
float time= (endTime - startTime) / 1000f;
logger.info("执行时间为:"+time+"s");
return aliyunOSS.getUrl()+objectName;
}
/**
* 删除文件
* @param key
*/
@Override
public boolean deleteFile(String key){
// 判断文件
if (StringUtils.isNotEmpty(key)) {
OSSClient client = new OSSClient(aliyunOSS.getEndpoint(), aliyunOSS.getAccessKeyId(), aliyunOSS.getAccessKeySecret());
try {
if (!client.doesBucketExist(aliyunOSS.getBucketName())){
logger.info("Bucket不存在");
return false;
}
client.deleteObject(aliyunOSS.getBucketName(), key);
logger.info("<----------OSS文件删-------->\n" + key);
}catch (Exception e){
e.printStackTrace();
logger.info("删除失败");
return false;
}finally {
if (client != null) {
client.shutdown();
}
}
}
return true;
}
/**实现并启动线程**/
private static class PartUploader implements Runnable {
private int i;//循环数
private long partSize; //分块大小
private int partCount; //分片总数
private MultipartFile file; //文件
private long fileLength;//文件大小
private AliyunOSS aliyunOSS; //上传OS配置
private String objectName;//文件名
private String uploadId;//它是分片上传事件的唯一标识,
private OSS ossClient;//阿里云OSS调用对象
private List<PartETag> partETags; // partETags是PartETag的集合。PartETag由分片的ETag和分片号组成。
public PartUploader(int i, long partSize, int partCount, MultipartFile file, long fileLength, AliyunOSS aliyunOSS, String objectName, String uploadId, OSS ossClient, List<PartETag> partETags) {
this.i = i;
this.partSize = partSize;
this.partCount = partCount;
this.file = file;
this.fileLength = fileLength;
this.aliyunOSS = aliyunOSS;
this.objectName = objectName;
this.uploadId = uploadId;
this.ossClient = ossClient;
this.partETags = partETags;
}
@Override
public void run() {
logger.info("\n上传当前分片数:"+(i+1));
InputStream instream=null;
try{
long startPos = i * partSize;
long curPartSize = (i + 1 == partCount) ? (fileLength - startPos) : partSize;
instream = file.getInputStream();
// 跳过已经上传的分片。
instream.skip(startPos);
UploadPartRequest uploadPartRequest = new UploadPartRequest();
uploadPartRequest.setBucketName(aliyunOSS.getBucketName());
uploadPartRequest.setKey(objectName);
uploadPartRequest.setUploadId(uploadId);
uploadPartRequest.setInputStream(instream);
// 设置分片大小。除了最后一个分片没有大小限制,其他的分片最小为100 KB。
uploadPartRequest.setPartSize(curPartSize);
// 设置分片号。每一个上传的分片都有一个分片号,取值范围是1~10000,如果超出这个范围,OSS将返回InvalidArgument的错误码。
uploadPartRequest.setPartNumber(i + 1);
// 每个分片不需要按顺序上传,甚至可以在不同客户端上传,OSS会按照分片号排序组成完整的文件。
UploadPartResult uploadPartResult = ossClient.uploadPart(uploadPartRequest);
//每次上传分片之后,OSS的返回结果会包含一个PartETag。PartETag将被保存到PartETags中。
synchronized (this.partETags) {
this.partETags.add(uploadPartResult.getPartETag());
}
} catch (Exception e) {
logger.error(e.getMessage());
} finally {
if(instream!=null){
try {
instream.close();
} catch (IOException e) {
logger.error(e.getMessage());
}
}
}
}
}
private String getFileName(MultipartFile file) {
//获取文件名
String filename = file.getOriginalFilename();
//获取最后一个.的位置
int lastIndexOf = filename.lastIndexOf(".");
//获取文件的后缀名
String suffix = filename.substring(lastIndexOf);
//上传到七牛的文件名
return UUID.randomUUID().toString()+suffix;
}
}
8.最后创建comtroller
package cc.flysoft.m.controller;
import cc.flysoft.m.service.AliyunOSSService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
/**
* @author zhanglin
* @descibe oss
* @date 2020/5/27 13:19
*/
@RestController
@Slf4j
public class UpLoadController {
@Autowired
private AliyunOSSServiceImpl aliyunOSSService;
/**
* 文件上传
*/
@RequestMapping(value = "/flyout/uploadFile")
public String uploadBlog(@RequestParam("file") MultipartFile file) {
String filename = file.getOriginalFilename();
System.out.println(filename + "==filename");
if (file != null) {
if (!"".equals(filename.trim())) {
// 上传到OSS
return aliyunOSSService.fragmentUpload(file);
}
}
return null;
}
}
全部代码已经贴出,前端页面需要自己取写。