阿里云OSS对象存储

一、介绍

    阿里云对象存储服务(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;
    }
}





猜你喜欢

转载自blog.csdn.net/hzs33/article/details/80136720