一、介绍
阿里云对象存储服务(Object Storage Service,简称 OSS),是阿里云提供的海量、安全、低成本、高可靠的云存储服务。它具有与平台无关的RESTful API接口,能够提供99.999999999%(11个9)的数据可靠性和99.95%的服务可用性。您可以在任何应用、任何时间、任何地点存储和访问任意类型的数据。
二、准备工作
1.购买阿里云服务
2.基本概念:bucket是一个阿里云的存储空间,上传文件到OSS被称为Object,object可以是虚拟文件夹,以/结尾就可以了,虚拟文件夹支持上传以及下载
阿里云上传后是不会返回路径给你的,路径有一定的规则:https://bucketName.endPoint/ke
三、上传文件
阿里云提供许多种上传文件的方式,java-sdk路径:https://help.aliyun.com/document_detail/31817.html?spm=a2c4g.11186623.6.539.NRKRNu
在项目中需要上传视频,估采用分片断点上传方式,这个方式步骤:
1.初始化一个分片上传任务(InitiateMultipartUpload)
2.逐个或并行上传分片(UploadPart)
3.完成分片上传(CompleteMultipartUpload)或取消分片上传(AbortMultipartUpload)
代码实现主要是在2的步骤使用多线程并行上传,使用Executors线程池框架,项目使用spring boot框架开发:
1.分片上传类:这里特别说明下:由于第2步使用多线程并发上传分片,而第3步的需要第2步返回来的参数,在线程的shutdown()后必须加上:
//阻塞,直到关闭后所有任务都已完成执行 pool.awaitTermination(9223372036854775807L, TimeUnit.SECONDS);否则会出现第2步还没有执行完,第3步就开始执行,到时上传失
/**
* Author: hezishan
* Date: 2018/4/27.
* Description: 分片上传类
* 1.初始化一个分片上传任务(InitiateMultipartUpload)
* 2.逐个或并行上传分片(UploadPart)
* 3.完成分片上传(CompleteMultipartUpload)或取消分片上传(AbortMultipartUpload)
**/
@Service
public class MultipartUpload {
public static final Logger LOGGER = Logger.getLogger(OSSUploadFile.class);
@Autowired
private OSSConfiguration ossConfiguration;
/**
* 1.初始化一个Multi-part upload请求
* @param client
* @param bucketName
* @param key
* @return
* @throws OSSException
* @throws ClientException
*/
public String initMultipartUpload(OSSClient client,String bucketName, String key) throws OSSException,ClientException{
InitiateMultipartUploadRequest initUploadRequest = new InitiateMultipartUploadRequest(bucketName, key);
InitiateMultipartUploadResult initResult = client.initiateMultipartUpload(initUploadRequest);
String uploadId = initResult.getUploadId();
return uploadId;
}
/**
* 2.逐个或并行上传分片
* @param ossClient
* @param bucketName
* @param key
* @param uploadFile
* @param sourcePath
* @param pool
* @return ResponseEntity
* @throws Exception
*/
public ResponseEntity uploadMultipartFile(OSSClient ossClient, String bucketName, String key, File uploadFile,String sourcePath,ExecutorService pool) throws Exception{
ResponseEntity responseEntity=new ResponseEntity();
//自定义的每个上传分块大小
Integer partSize=ossConfiguration.getUploadPartSize();
//需要上传的文件分块数
int partCount=FileUtils.calPartCount(uploadFile,partSize);
//分片上传唯一标识
String uploadId="";
//序列化的文件路径
String serializationFilePath=sourcePath+".up.temp";
boolean isSerializationFile=false;
//子线程池的线程对象封装类(用于序列化的)
UploadPartObj uploadPartObj=null;
//若存在上传失败留下的序列化文件则反序列化对象
if(new File(serializationFilePath).exists()){
uploadPartObj = (UploadPartObj)ObjectSerializableUtil.load(serializationFilePath);
isSerializationFile = true;
}
//序列化文件不存在,分配分块给子线程池线程对象
if(uploadPartObj==null||!isSerializationFile){
uploadPartObj = new UploadPartObj();
try {
//1.初始化MultipartUpload 返回uploadId
uploadId = initMultipartUpload(ossClient, bucketName, key);
} catch (OSSException |ClientException e) {
LOGGER.error("[MultipartUpload.class:uploadMultipartFile]:"+e.getMessage());
responseEntity.setResponseCode(ResponseCode.OSS_SUBMIT_ERROR);
responseEntity.setReponseMsg("OSS提交出错");
return responseEntity;
}
for (int i = 0; i < partCount ; i++) {
long start = partSize * i;
long curPartSize = partSize < uploadFile.length() - start ?
partSize : uploadFile.length() - start;
//2.构造上传线程,UploadPartThread是执行每个分块上传任务的线程(使用多线程并行上传)
uploadPartObj.getUploadPartThreads().add(new UploadPartThread(ossClient, bucketName, key,uploadFile, uploadId, i + 1,partSize * i, curPartSize));
}
}
try {
int i = 0;
//upload方法提交分块上传线程至子线程池上传,while循环用于上传失败重复上传,Constant.RETRY定义重复次数
while (upload(uploadPartObj,serializationFilePath,pool).isResult()==false) {
if(++i == ossConfiguration.getRetry()){
break;
}
}
} catch (Exception e) {
LOGGER.info("[MultipartUpload.class:uploadMultipartFile]:" + e.getMessage());
responseEntity.setResponseCode(ResponseCode.THREAD_ERROR);
responseEntity.setReponseMsg("线程出错");
return responseEntity;
}
if(!uploadPartObj.isResult()){
responseEntity.setResponseCode(ResponseCode.NETWORK_ERROR);
responseEntity.setReponseMsg("网络出错");
return responseEntity;
}
try {
//3.完成一个multi-part请求。
completeMultipartUpload(ossClient, bucketName, key, uploadPartObj);
} catch (Exception e) {
LOGGER.info("[MultipartUpload.class:uploadMultipartFile]:" + e.getMessage());
ObjectSerializableUtil.save(uploadPartObj,serializationFilePath);
responseEntity.setResponseCode(ResponseCode.OSS_SUBMIT_ERROR);
responseEntity.setReponseMsg("OSS提交出错");
return responseEntity;
}
String responseUrl=OSSUtils.generateResponseUrl(bucketName,ossConfiguration.getEndPoint(),key);
responseEntity.setResponseCode(ResponseCode.SUCCESS);
responseEntity.setReponseMsg("成功");
responseEntity.setResponseData(responseUrl);
return responseEntity;
}
/**
* 3.完成一个multi-part请求
* @param client
* @param bucketName
* @param key
* @param uploadPartObj
*/
public void completeMultipartUpload(OSSClient client, String bucketName, String key,UploadPartObj uploadPartObj){
List<PartETag> eTags = new ArrayList<PartETag>();
for (UploadPartThread uploadPartThread : uploadPartObj.getUploadPartThreads()) {
eTags.add(new PartETag(uploadPartThread.getMyPartETag().getPartNumber(),uploadPartThread.getMyPartETag().geteTag()));
}
//为part按partnumber排序
Collections.sort(eTags, new Comparator<PartETag>(){
@Override
public int compare(PartETag arg0, PartETag arg1) {
PartETag part1= arg0;
PartETag part2= arg1;
return part1.getPartNumber() - part2.getPartNumber();
}
});
CompleteMultipartUploadRequest completeMultipartUploadRequest =
new CompleteMultipartUploadRequest(bucketName, key, uploadPartObj.getUploadPartThreads().get(0).getUploadId(), eTags);
client.completeMultipartUpload(completeMultipartUploadRequest);
}
/**
* 多线程上传单个文件
* @param uploadPartObj
* @param serializationFilePath
* @return
*/
private UploadPartObj upload(UploadPartObj uploadPartObj,String serializationFilePath,ExecutorService pool){
try {
uploadPartObj.setResult(true);
//向子线程池中submit单个文件所有分块上传线程
for (int i=0;i<uploadPartObj.getUploadPartThreads().size();i++) {
if (uploadPartObj.getUploadPartThreads().get(i).getMyPartETag() == null) {
pool.submit(uploadPartObj.getUploadPartThreads().get(i));
}
}
//shutdown子线程池,池内所上传任务执行结束后停止当前线程池
pool.shutdown();
while (!pool.isTerminated()) {
//循环检查线程池,同时在此序列化uploadPartObj
ObjectSerializableUtil.save(uploadPartObj,serializationFilePath);
pool.awaitTermination(ossConfiguration.getSerializationTime(), TimeUnit.SECONDS);
}
//判断上传结果
for (UploadPartThread uploadPartThread: uploadPartObj.getUploadPartThreads()) {
if(uploadPartThread.getMyPartETag()==null) {
uploadPartObj.setResult(false);
}
}
//上传成功 删除序列化文件
if (uploadPartObj.isResult()==true){
ObjectSerializableUtil.delSerlzFile(serializationFilePath);
}
} catch (Exception e) {
LOGGER.info("[MultipartUpload.class:upload]:"+e.getMessage());
}
return uploadPartObj;
}
}
2.上传文件类:支持多文件并发上传,每个文件支持并发上传分片
/**
* Author: hezishan
* Date: 2018/4/26.
* Description: 上传文件
**/
@Service
public class OSSUploadFile implements Callable<ResponseEntity> {
public static final Logger LOGGER = Logger.getLogger(OSSUploadFile.class);
@Autowired
private ObjectManager objectManager;
@Autowired
private MultipartUpload multipartUpload;
@Autowired
private OSSConfiguration configuration;
//外部线程池
public static ExecutorService uploadMainPool=null;
static{
OSSConfiguration oss =(OSSConfiguration) SpringUtils.getBean("OSSConfiguration");
uploadMainPool=Executors.newFixedThreadPool(oss.getConcurrentFileNumber(), new ThreadFactory() {
@Override
public Thread newThread(Runnable r) {
Thread s = Executors.defaultThreadFactory().newThread(r);
s.setDaemon(true);
return s;
}
});
}
/**
* 内部线程池
*/
private ExecutorService pool;
/**
* 上传路径
*/
private String sourcePath;
/**
* buckername
*/
private String bucketName;
/**
* 云端存储路径
*/
private String key;
/**
* 执行当前线程
* @return
*/
public ResponseEntity uploadFile(String sourcePath,Integer uploadType){
//实例化单个文件上传线程
pool=Executors.newFixedThreadPool(configuration.getSingleFileConcrrentThreads());
this.sourcePath=sourcePath;
this.bucketName=configuration.getBucketName();
this.key=OSSUtils.generateKeyUrl(uploadType,sourcePath);
ResponseEntity responseEntity=new ResponseEntity();
//向uploadMainPool中submit当前线程
Future<ResponseEntity> result=uploadMainPool.submit(this);
try{
responseEntity=result.get();
}catch (Exception e){
LOGGER.info("[OSSUploadFile.class:uploadFile]:"+e);
responseEntity.setResponseCode(ResponseCode.ERROR);
responseEntity.setReponseMsg("执行上传文件出错");
return responseEntity;
}
return responseEntity;
}
/**
* 上传文件
* @return
*/
@Override
public ResponseEntity call() throws Exception {
ResponseEntity responseEntity=new ResponseEntity();
OSSClient ossClient=OSSClientFactory.getInstance();
File uploadFile=new File(sourcePath);
if(!uploadFile.exists()){
LOGGER.info("[OSSUploadFile.class:call]:"+sourcePath);
responseEntity.setResponseCode(ResponseCode.FILE_NOT_FOUND_ERROR);
responseEntity.setReponseMsg("文件未找到");
return responseEntity;
}
int re=objectManager.createBucket(ossClient,bucketName);
if(re==ResponseCode.NEW_BUCKET_ERROR){
responseEntity.setResponseCode(ResponseCode.NEW_BUCKET_ERROR);
responseEntity.setReponseMsg("创建bucket错误");
return responseEntity;
}
//分片续点上传
responseEntity=multipartUpload.uploadMultipartFile(ossClient,bucketName,key,uploadFile,sourcePath,pool);
pool=null;
return responseEntity;
}
4.使用线程安全的单例模式获取OSSClient
/**
* Author: hezishan
* Date: 2018/4/25.
* Description: OSSClientFactory OSSClient工厂类
**/
@Component
public class OSSClientFactory {
private static OSSClient ossClient = null;
/**
* 获取一个ossclient对象
* @return
*/
public static OSSClient getInstance() {
try{
synchronized (OSSClientFactory.class) {
if (ossClient == null) {
//加载配置文件
OSSConfiguration ossConfiguration =(OSSConfiguration) SpringUtils.getBean("OSSConfiguration");
if(ossConfiguration!=null){
ossClient = new OSSClient(ossConfiguration.getEndPoint(),ossConfiguration.getAccessKeyId(), ossConfiguration.getAccessKeySecret());
}
}
}
}catch (Exception e){
e.printStackTrace();
}
return ossClient;
}
}
5.其他工具类:
MyPartETag:是对步骤2返回的数据进行封装,并在第3步使用:
/**
* Author: hezishan
* Date: 2018/4/26.
* Description: MyPartETag类
* 用于保存分片上传的PartETag
**/
public class MyPartETag implements Serializable {
private static final long serialVersionUID = 1L;
private int partNumber;
private String eTag;
public MyPartETag(PartETag partETag ) {
super();
this.partNumber = partETag.getPartNumber();
this.eTag = partETag.getETag();
}
public int getPartNumber() {
return partNumber;
}
public void setPartNumber(int partNumber) {
this.partNumber = partNumber;
}
public String geteTag() {
return eTag;
}
public void seteTag(String eTag) {
this.eTag = eTag;
}
}
UploadPartObj
public class UploadPartObj implements Serializable {
private static final long serialVersionUID = 1L;
List<UploadPartThread> uploadPartThreads = Collections.synchronizedList(new ArrayList<UploadPartThread>());
boolean result = true;
public List<UploadPartThread> getUploadPartThreads() {
return uploadPartThreads;
}
public void setUploadPartThreads(List<UploadPartThread> uploadPartThreads) {
this.uploadPartThreads = uploadPartThreads;
}
public boolean isResult() {
return result;
}
public void setResult(boolean result) {
this.result = result;
}
}
UploadPartThread
/**
* Author: hezishan
* Date: 2018/4/26.
* Description:上传的分片线程类
**/
public class UploadPartThread implements Callable<UploadPartThread>,Serializable {
private static final long serialVersionUID = 1L;
public static final Logger LOGGER = Logger.getLogger(UploadPartThread.class);
private OSSClient ossClient;
private File uploadFile;
private String bucket;
private String object;
private long start;
private long size;
private int partId;
private String uploadId;
private MyPartETag myPartETag;
public UploadPartThread(OSSClient ossClient, String bucket, String object,
File uploadFile, String uploadId, int partId,
long start, long partSize) {
this.ossClient=ossClient;
this.uploadFile = uploadFile;
this.bucket = bucket;
this.object = object;
this.start = start;
this.size = partSize;
this.partId = partId;
this.uploadId = uploadId;
}
@Override
public UploadPartThread call() {
InputStream in = null;
try {
in = new FileInputStream(uploadFile);
in.skip(start);
UploadPartRequest uploadPartRequest = new UploadPartRequest();
uploadPartRequest.setBucketName(bucket);
uploadPartRequest.setKey(object);
uploadPartRequest.setUploadId(uploadId);
uploadPartRequest.setInputStream(in);
uploadPartRequest.setPartSize(size);
uploadPartRequest.setPartNumber(partId);
//MyPartETag是对uploadPartResult.getPartETag()的返回值PartETag的封装,主要是为了能序列化PartETag,MyPartETag仅比PartETag多实现了Serializable接口
UploadPartResult uploadPartResult = ossClient.uploadPart(uploadPartRequest);
myPartETag = new MyPartETag(uploadPartResult.getPartETag());
} catch (Exception e) {
LOGGER.error("[UploadPartThread.class:UploadPartThread]:"+e.getMessage());
} finally {
if (in != null){
try {
in.close();
} catch (Exception e) {
LOGGER.error("[UploadPartThread.class:UploadPartThread]:"+e.getMessage());
}
}
}
return this;
}
public String getUploadId() {
return uploadId;
}
public void setUploadId(String uploadId) {
this.uploadId = uploadId;
}
public MyPartETag getMyPartETag() {
return myPartETag;
}
public void setMyPartETag(MyPartETag myPartETag) {
this.myPartETag = myPartETag;
}
}