java中oss分片上傳(包含業(yè)務(wù)和詳細(xì)講解)

背景:

前端上傳音視頻文件過(guò)大大于100MB。討論后決定采用oss分片上傳。

業(yè)務(wù)流程:

前端先調(diào)用一次初始化接口拿到本次分片任務(wù)的唯一分片id。前端負(fù)責(zé)分片,傳參:總片數(shù)、第幾片,唯一分片id等數(shù)據(jù),這些需要傳給后臺(tái),后臺(tái)才能夠以此判斷。下面是demo:

導(dǎo)maven包:注意需要3以上的版本

<!-- 阿里云對(duì)象存儲(chǔ)服務(wù) -->

<dependency>

? ? <groupId>com.aliyun.oss</groupId>

? ? <artifactId>aliyun-sdk-oss</artifactId>

? ? <version>3.15.1</version>

</dependency>

配置:

####################################### 阿里云對(duì)象存儲(chǔ)配置 #######################################

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

業(yè)務(wù)實(shí)現(xiàn):使用了ossUtil工具類(lèi)

/**

? ? * 我們先初始化拿到分片唯一ID,返回給前端

? ? * @param param

? ? * @return

? ? */

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

? ? @PostMapping("/initTest")

? ? public UmsAdminLoginLogDO testInitControl(@RequestBody UmsAdminLoginLogDO param) {

? ? ? ? //分片上傳

? ? ? ? UmsAdminLoginLogDO result = new UmsAdminLoginLogDO();

? ? ? ? // 生成任務(wù)id

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

? ? ? ? result.setTaskId(taskId);

? ? ? ? //生成任務(wù)名稱,建議使用各種ID拼接

? ? ? ? String taskKey = param.getFileName() + taskId;

? ? ? ? // 請(qǐng)求阿里云oss獲取分片唯一ID

? ? ? ? String ossSlicesId = ossUtil.getUploadId(taskKey);

? ? ? ? result.setOssSlicesId(ossSlicesId);

? ? ? ? //每一片的大小

? ? ? ? result.setMinSliceSize("100k");

? ? ? ? redisUtil.set(ossSlicesId,result);

? ? ? ? return result;

? ? }

分片上傳:

/**

* 有些必傳的參數(shù)比如分片id,總片數(shù),第幾片,文件流數(shù)據(jù)源

* @param param

* @throws Exception

*/

@ApiOperation("oss分片上傳")

@PostMapping("/uploadTest")

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

? ? //必須求出redis中的PartETags,在分片合成文件中需要以此為依據(jù),合并文件返回最終地址

? ? 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();

? ? //字節(jié)流轉(zhuǎn)換

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

? ? Map<Integer, PartETag> partETags = param.getPartETags();

? ? //分片上傳

? ? try {

? ? ? ? //每次上傳分片之后,OSS的返回結(jié)果會(huì)包含一個(gè)PartETag

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

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

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

? ? ? ? //分片編號(hào)等于總片數(shù)的時(shí)候合并文件,如果符合條件則合并文件,否則繼續(xù)等待

? ? ? ? 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());

? ? }

}

工具類(lèi):

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);

? ? }

? ? /**

? ? * 分塊上傳完成獲取結(jié)果

? ? */

? ? 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? 文件流數(shù)據(jù)

? ? * @param uploadId oss唯一分片id

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

? ? * @param partNum? 第幾片

? ? * @param partSize 總片數(shù)

? ? * @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();

? ? }

? ? /**

? ? * 分塊上傳完成獲取結(jié)果

? ? */

? ? public String getUploadId(String fileKey) {

? ? ? ? InitiateMultipartUploadRequest request = new InitiateMultipartUploadRequest(bucketName, fileKey);

? ? ? ? // 初始化分片

? ? ? ? InitiateMultipartUploadResult unrest = ossClient.initiateMultipartUpload(request);

? ? ? ? // 返回uploadId,它是分片上傳事件的唯一標(biāo)識(shí),您可以根據(jù)這個(gè)ID來(lái)發(fā)起相關(guān)的操作,如取消分片上傳、查詢分片上傳等。

? ? ? ? String uploadId = unrest.getUploadId();

? ? ? ? return uploadId;

? ? }

? ? /**

? ? * 獲取bucket文件的下載鏈接

? ? *

? ? * @param pathFile? 首字母不帶/的路徑和文件

? ? * @param bucketName

? ? * @return 上報(bào)返回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? ? ? 文件字節(jié)流

? ? * @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 {

? ? ? ? ? ? // 創(chuàng)建上傳Object的Metadata

? ? ? ? ? ? ObjectMetadata objectMetadata = new ObjectMetadata();

? ? ? ? ? ? objectMetadata.setContentLength(in.available());

? ? ? ? ? ? objectMetadata.setCacheControl("no-cache");// 設(shè)置Cache-Control請(qǐng)求頭,表示用戶指定的HTTP請(qǐng)求/回復(fù)鏈的緩存行為:不經(jīng)過(guò)本地緩存

? ? ? ? ? ? objectMetadata.setHeader("Pragma", "no-cache");// 設(shè)置頁(yè)面不緩存

? ? ? ? ? ? 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服務(wù)文件上傳時(shí)文件的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";

? ? ? ? }

? ? }

}

實(shí)體類(lèi)對(duì)象:

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 {

? ? /**

? ? * 初始化任務(wù)id

? ? */

? ? private String taskId;

? ? /**

? ? * 上傳文件類(lèi)型

? ? */

? ? private String fileType;

? ? /**

? ? * 文件總片數(shù)

? ? */

? ? private Integer fileSlicesNum;

? ? /**

? ? * 分片編號(hào)(1-10000有序的編號(hào),越大的編號(hào)位置越靠后)

? ? */

? ? private Integer sliceNo;

? ? /**

? ? * 本次請(qǐng)求文件的md5值

? ? */

? ? private String fileMD5;

? ? /**

? ? *文件流數(shù)據(jù)

? ? */

? ? private byte[] content;

? ? /**

? ? * 文件名稱

? ? */

? ? private String fileName;

? ? /**

? ? * oss初始化分片id

? ? */

? ? private String ossSlicesId;

? ? /**

? ? * 最小分片大?。ǚ制蟼魇浅詈笠黄猓渌募坏眯∮谠撝担?/p>

? ? */

? ? private String minSliceSize;

? ? Map<Integer, PartETag> partETags = new HashMap<>(16);

}

文件流數(shù)據(jù):content,可以換成file等類(lèi)型,最后轉(zhuǎn)換成oss所需文件流即可,合格的程序員應(yīng)當(dāng)學(xué)會(huì)靈活應(yīng)變相關(guān)代碼,哈哈哈。

結(jié)語(yǔ):其實(shí)分片上傳和普通的上傳只是多了一個(gè)合并文件的步驟,其他的都是差不多;因?yàn)檠芯繒r(shí)間較短,還有些資料沒(méi)有查出,比如PartETag這代表含義等。有需要補(bǔ)充的歡迎在下面補(bǔ)充。

創(chuàng)作不易,如果這篇文章對(duì)你有用,請(qǐng)點(diǎn)個(gè)贊謝謝?(?ω?)?!

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容