java中oss分片上传(包含业务和详细讲解)

背景:

前端上传音视频文件过大大于100MB。讨论后决定采用oss分片上传。

业务流程:

前端先调用一次初始化接口拿到本次分片任务的唯一分片id。前端负责分片,传参:总片数、第几片,唯一分片id等数据,这些需要传给后台,后台才能够以此判断。下面是demo:

导maven包:注意需要3以上的版本

    com.aliyun.oss

    aliyun-sdk-oss

    3.15.1

配置:

####################################### 阿里云对象存储配置 #######################################

oss.endpoint.ext = oss-cn-zhangjiakou.aliyuncs.com

oss.endpoint.internal = oss-cn-zhangjiakou.aliyuncs.com

oss.accessKeyId = LTAI4FcG6N5FEUt38DQdHGE1

oss.accessKeySecret = KEBGjLMlLLvMLMi2FQ1GRZ825rUywh

oss.bucketName = dev-iot-services-public

业务实现:使用了ossUtil工具类

/**

    * 我们先初始化拿到分片唯一ID,返回给前端

    * @param param

    * @return

    */

    @ApiOperation("oss初始化分片")

    @PostMapping("/initTest")

    public UmsAdminLoginLogDO testInitControl(@RequestBody UmsAdminLoginLogDO param) {

        //分片上传

        UmsAdminLoginLogDO result = new UmsAdminLoginLogDO();

        // 生成任务id

        String taskId = UUID.randomUUID().toString().replaceAll("-", "");

        result.setTaskId(taskId);

        //生成任务名称,建议使用各种ID拼接

        String taskKey = param.getFileName() + taskId;

        // 请求阿里云oss获取分片唯一ID

        String ossSlicesId = ossUtil.getUploadId(taskKey);

        result.setOssSlicesId(ossSlicesId);

        //每一片的大小

        result.setMinSliceSize("100k");

        redisUtil.set(ossSlicesId,result);

        return result;

    }

分片上传:

/**

* 有些必传的参数比如分片id,总片数,第几片,文件流数据源

* @param param

* @throws Exception

*/

@ApiOperation("oss分片上传")

@PostMapping("/uploadTest")

public void testControl(@RequestBody UmsAdminLoginLogDO param) throws Exception {

    //必须求出redis中的PartETags,在分片合成文件中需要以此为依据,合并文件返回最终地址

    UmsAdminLoginLogDO redisParam = (UmsAdminLoginLogDO) redisUtil.get(param.getOssSlicesId());

    if (redisParam !=null) {

        param.setPartETags(redisParam.getPartETags());

    }

    int sliceNo = param.getSliceNo();

    int fileSlicesNum = param.getFileSlicesNum();

    String ossSlicesId = param.getOssSlicesId();

    //字节流转换

    InputStream inputStream = new ByteArrayInputStream(param.getContent());

    Map partETags = param.getPartETags();

    //分片上传

    try {

        //每次上传分片之后,OSS的返回结果会包含一个PartETag

        PartETag partETag = ossUtil.partUploadFile(param.getFileName(), inputStream, ossSlicesId,

                param.getFileMD5(), param.getSliceNo(), param.getContent().length);

        partETags.put(param.getSliceNo(), partETag);

        //分片编号等于总片数的时候合并文件,如果符合条件则合并文件,否则继续等待

        if (fileSlicesNum==sliceNo) {

            //合并文件,注意:partETags必须是所有分片的所以必须存入redis,然后取出放入集合

            String url = ossUtil.completePartUploadFile(param.getFileName(), ossSlicesId,

                    new ArrayList<>(partETags.values()));

            //oss地址返回后存入并清除redis

            param.setFileUrl(url);

            redisUtil.del(ossSlicesId);

        }else {

            redisUtil.set(param.getOssSlicesId(), param);

        }

    } catch (Exception e) {

        throw new Exception(ErrorCodeEnum.SYSTEM_ERROR.getMsg());

    }

}

工具类:

package com.macro.mall.tiny.demo.utils;

import com.aliyun.oss.OSSClientBuilder;

import com.aliyun.oss.OSS;

import com.aliyun.oss.OSSClient;

import com.aliyun.oss.model.*;

import org.apache.commons.lang3.StringUtils;

import org.slf4j.Logger;

import org.slf4j.LoggerFactory;

import org.springframework.beans.factory.annotation.Value;

import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;

import java.io.IOException;

import java.io.InputStream;

import java.util.List;

/**

* @author zhangtonghao

* @create 2022-08-30 16:26

*/

@Component

public class OSSUtil {

    private static Logger logger = LoggerFactory.getLogger(OSSUtil.class);

    // private OSSClient ossClient;

    @Value("${oss.endpoint.ext}")

    private String endpoint;

    @Value("${oss.endpoint.internal}")

    private String internalEndpoint;

    @Value("${oss.accessKeyId}")

    private String accessKeyId;

    @Value("${oss.accessKeySecret}")

    private String accessKeySecret;

    @Value("${oss.bucketName}")

    private String bucketName;

    private OSS ossClient;

    @PostConstruct

    public void init() {

        ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);

    }

    /**

    * 分块上传完成获取结果

    */

    public String completePartUploadFile(String fileKey, String uploadId, List partETags) {

        CompleteMultipartUploadRequest request = new CompleteMultipartUploadRequest(bucketName, fileKey, uploadId,

                partETags);

        ossClient.completeMultipartUpload(request);

        String downLoadUrl = getDownloadUrl(fileKey, bucketName);

        logger.debug("-------------- 文件的下载URL ------------" + downLoadUrl);

        return downLoadUrl;

    }

    /**

    *

    * @param fileKey  文件名称

    * @param is  文件流数据

    * @param uploadId oss唯一分片id

    * @param fileMd5 文件的md5值(非必传)

    * @param partNum  第几片

    * @param partSize 总片数

    * @return

    */

    public PartETag partUploadFile(String fileKey, InputStream is, String uploadId, String fileMd5, int partNum,

                                  long partSize) {

        UploadPartRequest uploadPartRequest = new UploadPartRequest();

        uploadPartRequest.setBucketName(bucketName);

        uploadPartRequest.setUploadId(uploadId);

        uploadPartRequest.setPartNumber(partNum);

        uploadPartRequest.setPartSize(partSize);

        uploadPartRequest.setInputStream(is);

        uploadPartRequest.setKey(fileKey);

        uploadPartRequest.setMd5Digest(fileMd5);

        UploadPartResult uploadPartResult = ossClient.uploadPart(uploadPartRequest);

        return uploadPartResult.getPartETag();

    }

    /**

    * 分块上传完成获取结果

    */

    public String getUploadId(String fileKey) {

        InitiateMultipartUploadRequest request = new InitiateMultipartUploadRequest(bucketName, fileKey);

        // 初始化分片

        InitiateMultipartUploadResult unrest = ossClient.initiateMultipartUpload(request);

        // 返回uploadId,它是分片上传事件的唯一标识,您可以根据这个ID来发起相关的操作,如取消分片上传、查询分片上传等。

        String uploadId = unrest.getUploadId();

        return uploadId;

    }

    /**

    * 获取bucket文件的下载链接

    *

    * @param pathFile  首字母不带/的路径和文件

    * @param bucketName

    * @return 上报返回null, 成功返回地址

    */

    public String getDownloadUrl(String pathFile, String bucketName) {

        if (bucketName == null || "".equals(bucketName)) {

            bucketName = bucketName;

        }

        StringBuffer url = new StringBuffer();

        url.append("http://").append(bucketName).append(endpoint).append("/");

        if (pathFile != null && !"".equals(pathFile)) {

            url.append(pathFile);

        }

        return url.toString();

    }

    /**

    * 上传文件到阿里云,并生成url

    *

    * @param filedir (key)文件名(不包括后缀)

    * @param in      文件字节流

    * @return String 生成的文件url

    */

    public String uploadToAliyun(String filedir, InputStream in, String fileName, boolean isRandomName) {

        String suffix = fileName.substring(fileName.lastIndexOf(".") + 1);

        if (isRandomName) {

            fileName = UUIDGenerator.generateCommonUUID() + "." + suffix;

        }

        logger.debug("------------>文件名称为:  " + fileName);

        OSSClient ossClient = new OSSClient(internalEndpoint, accessKeyId, accessKeySecret);

        String url = null;

        try {

            // 创建上传Object的Metadata

            ObjectMetadata objectMetadata = new ObjectMetadata();

            objectMetadata.setContentLength(in.available());

            objectMetadata.setCacheControl("no-cache");// 设置Cache-Control请求头,表示用户指定的HTTP请求/回复链的缓存行为:不经过本地缓存

            objectMetadata.setHeader("Pragma", "no-cache");// 设置页面不缓存

            objectMetadata.setContentType(getcontentType(suffix));

            objectMetadata.setContentDisposition("inline;filename=" + fileName);

            // 上传文件

            ossClient.putObject(bucketName, filedir + "/" + fileName, in, objectMetadata);

            url = buildUrl(filedir + "/" + fileName);

        } catch (IOException e) {

            logger.error("error", e);

        } finally {

            ossClient.shutdown();

            try {

                if (in != null) {

                    in.close();

                }

            } catch (IOException e) {

                logger.error("error", e);

            }

        }

        return url;

    }

    private String buildUrl(String fileDir) {

        StringBuffer url = new StringBuffer();

        if (org.apache.commons.lang3.StringUtils.isEmpty(bucketName)) {

            logger.error("bucketName为空");

            return null;

        }

        if (org.apache.commons.lang3.StringUtils.isEmpty(endpoint)) {

            logger.error("endpoint为空");

            return null;

        }

        if (StringUtils.isEmpty(endpoint)) {

            logger.error("上传文件目录为空");

            return null;

        }

        url.append("https://").append(bucketName).append(".").append(endpoint).append("/").append(fileDir);

        return url.toString();

    }

    /**

    * 删除图片

    *

    * @param key

    */

    public void deletePicture(String key) {

        OSSClient ossClient = new OSSClient(endpoint, accessKeyId, accessKeySecret);

        ossClient.deleteObject(bucketName, key);

        ossClient.shutdown();

    }

    /**

    * Description: 判断OSS服务文件上传时文件的contentType

    *

    * @param suffix 文件后缀

    * @return String HTTP Content-type

    */

    public String getcontentType(String suffix) {

        if (suffix.equalsIgnoreCase("bmp")) {

            return "image/bmp";

        } else if (suffix.equalsIgnoreCase("gif")) {

            return "image/gif";

        } else if (suffix.equalsIgnoreCase("jpeg") || suffix.equalsIgnoreCase("jpg")) {

            return "image/jpeg";

        } else if (suffix.equalsIgnoreCase("png")) {

            return "image/png";

        } else if (suffix.equalsIgnoreCase("html")) {

            return "text/html";

        } else if (suffix.equalsIgnoreCase("txt")) {

            return "text/plain";

        } else if (suffix.equalsIgnoreCase("vsd")) {

            return "application/vnd.visio";

        } else if (suffix.equalsIgnoreCase("pptx") || suffix.equalsIgnoreCase("ppt")) {

            return "application/vnd.ms-powerpoint";

        } else if (suffix.equalsIgnoreCase("docx") || suffix.equalsIgnoreCase("doc")) {

            return "application/msword";

        } else if (suffix.equalsIgnoreCase("xls") || suffix.equalsIgnoreCase("xlsx")) {

            return "application/vnd.ms-excel";

        } else if (suffix.equalsIgnoreCase("xml")) {

            return "text/xml";

        } else if (suffix.equalsIgnoreCase("mp3")) {

            return "audio/mp3";

        } else if (suffix.equalsIgnoreCase("amr")) {

            return "audio/amr";

        } else if (suffix.equalsIgnoreCase("pdf")) {

            return "application/pdf";

        } else {

            return "text/plain";

        }

    }

}

实体类对象:

package com.macro.mall.tiny.demo.model.po.mall;

import com.aliyun.oss.model.PartETag;

import lombok.Data;

import java.util.Date;

import java.util.HashMap;

import java.util.Map;

/**

* @author zhangtonghao

* @create 2022-04-06 15:08

*/

@Data

public class UmsAdminLoginLogDO {

    /**

    * 初始化任务id

    */

    private String taskId;

    /**

    * 上传文件类型

    */

    private String fileType;

    /**

    * 文件总片数

    */

    private Integer fileSlicesNum;

    /**

    * 分片编号(1-10000有序的编号,越大的编号位置越靠后)

    */

    private Integer sliceNo;

    /**

    * 本次请求文件的md5值

    */

    private String fileMD5;

    /**

    *文件流数据

    */

    private byte[] content;

    /**

    * 文件名称

    */

    private String fileName;

    /**

    * oss初始化分片id

    */

    private String ossSlicesId;

    /**

    * 最小分片大小(分片上传是除最后一片外,其他文件不得小于该值)

    */

    private String minSliceSize;

    Map partETags = new HashMap<>(16);

}

文件流数据:content,可以换成file等类型,最后转换成oss所需文件流即可,合格的程序员应当学会灵活应变相关代码,哈哈哈。

结语:其实分片上传和普通的上传只是多了一个合并文件的步骤,其他的都是差不多;因为研究时间较短,还有些资料没有查出,比如PartETag这代表含义等。有需要补充的欢迎在下面补充。

创作不易,如果这篇文章对你有用,请点个赞谢谢♪(・ω・)ノ!

你可能感兴趣的:(java中oss分片上传(包含业务和详细讲解))