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

背景:

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

业务流程:

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

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

<!-- 阿里云对象存储服务 -->
<dependency>
    <groupId>com.aliyun.oss</groupId>
    <artifactId>aliyun-sdk-oss</artifactId>
    <version>3.15.1</version>
</dependency>

配置:

####################################### 阿里云对象存储配置 #######################################
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<Integer, PartETag> 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<PartETag> 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<Integer, PartETag> partETags = new HashMap<>(16);


}

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

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

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

  • 5
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
Java SDK for OSS 提供了分片上传对象的功能,可以将大文件切分成多个小块进行上传,以提高上传速度和可靠性。以下是 Java SDK for OSS 分片上传的简单示例: ```java import com.aliyun.oss.OSS; import com.aliyun.oss.OSSClientBuilder; import com.aliyun.oss.model.*; import java.io.File; import java.util.ArrayList; import java.util.List; public class OSSMultipartUploadDemo { private static String endpoint = "yourEndpoint"; private static String accessKeyId = "yourAccessKeyId"; private static String accessKeySecret = "yourAccessKeySecret"; private static String bucketName = "yourBucketName"; private static String objectName = "yourObjectName"; private static String uploadFilePath = "yourUploadFilePath"; public static void main(String[] args) { // 创建OSSClient实例 OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret); // 初始化分片上传 InitiateMultipartUploadRequest initiateMultipartUploadRequest = new InitiateMultipartUploadRequest(bucketName, objectName); InitiateMultipartUploadResult initiateMultipartUploadResult = ossClient.initiateMultipartUpload(initiateMultipartUploadRequest); String uploadId = initiateMultipartUploadResult.getUploadId(); // 计算文件分片数 final long partSize = 1024 * 1024L; // 设置分片大小为1MB final File uploadFile = new File(uploadFilePath); long fileLength = uploadFile.length(); int partCount = (int) (fileLength / partSize); if (fileLength % partSize != 0) { partCount++; } // 上传分片 List<PartETag> partETags = new ArrayList<>(); for (int i = 0; i < partCount; i++) { long startPos = i * partSize; long curPartSize = (i + 1 == partCount) ? fileLength - startPos : partSize; UploadPartRequest uploadPartRequest = new UploadPartRequest(); uploadPartRequest.setBucketName(bucketName); uploadPartRequest.setKey(objectName); uploadPartRequest.setUploadId(uploadId); uploadPartRequest.setPartNumber(i + 1); uploadPartRequest.setInputStream(FileUtils.openInputStream(uploadFile)); uploadPartRequest.setPartSize(curPartSize); uploadPartRequest.setOffset(startPos); UploadPartResult uploadPartResult = ossClient.uploadPart(uploadPartRequest); partETags.add(uploadPartResult.getPartETag()); } // 完成分片上传 CompleteMultipartUploadRequest completeMultipartUploadRequest = new CompleteMultipartUploadRequest(bucketName, objectName, uploadId, partETags); CompleteMultipartUploadResult completeMultipartUploadResult = ossClient.completeMultipartUpload(completeMultipartUploadRequest); // 关闭OSSClient ossClient.shutdown(); System.out.println("File upload successfully!"); } } ``` 以上代码,首先创建了一个 OSSClient 实例,然后调用 `initiateMultipartUpload` 方法初始化分片上传,并保存返回的 `uploadId`。接下来计算文件分片数,循环上传每个分片,并将每个分片的 `PartETag` 对象添加到 `partETags` 列表。最后调用 `completeMultipartUpload` 方法完成分片上传。 需要注意的是,OSS 对象的大小必须大于100KB,否则不能进行分片上传。而且,分片上传必须按照指定的顺序上传,每个分片的大小必须相同。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

你可以叫我老白

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值