目錄
- 5.10 介面開發-分片上傳
- 5.10.1 分片上傳介紹
- 5.10.2 前端分片上傳插件webuploader
- 5.10.3 后端代碼實作
- 5.10.3.1 介面檔案
- 5.10.3.2 代碼開發
- 5.10.3.3 介面測驗
- 5.11 介面開發-分片合并
- 5.11.1 FileChunkStrategy
- 5.11.2 AbstractFileChunkStrategy
- 5.11.3 LocalChunkServiceImpl
- 5.11.4 FastDfsChunkServiceImpl
- 5.11.5 AliChunkServiceImpl
- 5.11.6 MinioChunkServiceImpl
- 5.11.7 分片合并介面
5.10 介面開發-分片上傳
第2-1-2章 傳統方式安裝FastDFS-附FastDFS常用命令
第2-1-3章 docker-compose安裝FastDFS,實作檔案存盤服務
第2-1-5章 docker安裝MinIO實作檔案存盤服務-springboot整合minio-minio全網最全的資料
全套代碼及資料全部完整提供,點此處下載
5.10.1 分片上傳介紹
前面我們已經實作了普通的附件服務和網盤服務,如果上傳的檔案比較小,可以直接使用這兩個服務即可,如果上傳的檔案比較大,例如要上傳一個500M或者1G的視頻檔案(或者更大),這就需要分片上傳了,那么什么是分片上傳呢?
分片上傳就是把一個大檔案進行分片,一片一片的上傳到服務端,最后由服務端進行分片的合并,
要實作分片上傳需要前端和后端配合來完成,在進行分片上傳時,一般是由前端對要上傳的大檔案進行分片,然后分多次將這些分片上傳到服務端,所有分片都上傳到服務端后,在服務端將分片合并為原始的大檔案,采用大檔案分片并發上傳,可以極大的提高檔案的上傳效率,
5.10.2 前端分片上傳插件webuploader
WebUploader是由Baidu WebFE(FEX)團隊開發的一個簡單的以HTML5為主,FLASH為輔的現代檔案上傳組件,在現代的瀏覽器里面能充分發揮HTML5的優勢,同時又不摒棄主流IE瀏覽器,沿用原來的FLASH運行時,兼容IE6+,iOS 6+, android 4+,
官網地址:http://fex.baidu.com/webuploader/
分片與并發結合,將一個大檔案分割成多塊,并發上傳,極大地提高大檔案的上傳速度,
當網路問題導致傳輸錯誤時,只需要重傳出錯分片,而不是整個檔案,另外分片傳輸能夠更加實時的跟蹤上傳進度,
由于本文展示的主要為后端服務開發,所以前端部分不再開發,直接從資料中獲得使用即可,
資料位置:檔案服務\資料\分片上傳\前端
直接打開index.html頁面,選擇要上傳的大檔案,可以看到發送了多次請求,每次請求會上傳此大檔案的一個分片:

注:由于目前后端服務還沒有開發,所以上傳會失敗,
5.10.3 后端代碼實作
5.10.3.1 介面檔案


5.10.3.2 代碼開發
第一步:創建FileChunkController并提供分片上傳方法uploadFile
package com.itheima.pinda.file.controller;
import com.itheima.pinda.base.BaseController;
import com.itheima.pinda.base.R;
import com.itheima.pinda.dozer.DozerUtils;
import com.itheima.pinda.file.domain.FileAttrDO;
import com.itheima.pinda.file.dto.chunk.FileChunksMergeDTO;
import com.itheima.pinda.file.dto.chunk.FileUploadDTO;
import com.itheima.pinda.file.entity.File;
import com.itheima.pinda.file.manager.WebUploader;
import com.itheima.pinda.file.properties.FileServerProperties;
import com.itheima.pinda.file.service.FileService;
import com.itheima.pinda.file.strategy.FileChunkStrategy;
import com.itheima.pinda.file.strategy.FileStrategy;
import com.itheima.pinda.file.utils.FileDataTypeUtil;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
/**
* 分片上傳
*/
@RestController
@Slf4j
@RequestMapping("/chunk")
@CrossOrigin
@Api(value = "https://www.cnblogs.com/gitBook/p/分片上傳", tags = "分片上傳,需要webuploder.js插件進行配合使用")
public class FileChunkController extends BaseController {
@Autowired
private FileServerProperties fileProperties;
@Autowired
private FileService fileService;
@Autowired
private FileStrategy fileStrategy;
@Autowired
private WebUploader webUploader;
@Autowired
private DozerUtils dozerUtils;
/**
* 分片上傳
* @param fileUploadDTO
* @param multipartFile
* @return
*/
@ApiOperation(value = "https://www.cnblogs.com/gitBook/p/分片上傳", notes = "分片上傳")
@PostMapping(value = "https://www.cnblogs.com/upload")
public R<FileChunksMergeDTO> uploadFile(FileUploadDTO fileUploadDTO,
@RequestParam(value = "https://www.cnblogs.com/gitBook/p/file", required = false) MultipartFile multipartFile) throws Exception {
if (multipartFile == null || multipartFile.isEmpty()) {
log.error("分片上傳分片為空");
return fail("分片上傳分片為空");
}
// 存放分片檔案的服務器絕對路徑 ,例如 D:\\uploadfiles\\2020\\04
String uploadFolder = FileDataTypeUtil.getUploadPathPrefix(fileProperties.getStoragePath());
if (fileUploadDTO.getChunks() == null || fileUploadDTO.getChunks() <= 0) {
//沒有分片,按照普通檔案上傳處理
File file = fileStrategy.upload(multipartFile);
file.setFileMd5(fileUploadDTO.getMd5());
fileService.save(file);
return success(null);
} else {
//為上傳的檔案準備好對應的位置
java.io.File targetFile = webUploader.getReadySpace(fileUploadDTO, uploadFolder);
if (targetFile == null) {
return fail("分片上傳失敗");
}
//保存上傳檔案
multipartFile.transferTo(targetFile);
//封裝資訊給前端,用于分片合并
FileChunksMergeDTO mergeDTO = new FileChunksMergeDTO();
mergeDTO.setSubmittedFileName(multipartFile.getOriginalFilename());
dozerUtils.map(fileUploadDTO,mergeDTO);
return success(mergeDTO);
}
}
}
第二步:在配置屬性類中添加storagePath屬性和對于的get、set方法
public String getStoragePath() {
return storagePath;
}
public void setStoragePath(String storagePath) {
this.storagePath = storagePath;
}
//指定分片上傳時臨時存放目錄
private String storagePath ;
第三步:創建WebUploader分片上傳工具類
package com.itheima.pinda.file.manager;
import com.itheima.pinda.file.dto.chunk.FileUploadDTO;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import java.io.IOException;
/**
* 分片上傳工具類
*/
@Service
@Slf4j
public class WebUploader2 {
/**
* 為上傳的檔案創建對應的保存位置,若上傳的是分片,則會創建對應的檔案夾結構和tmp檔案
*
* @param fileUploadDTO 上傳檔案的相關資訊
* @param path 檔案保存根路徑
* @return
*/
public java.io.File getReadySpace(FileUploadDTO fileUploadDTO, String path) {
//創建上傳檔案所需的檔案夾
if (!this.createFileFolder(path, false)) {
return null;
}
//將上傳的分片保存在此目錄中
String fileFolder = fileUploadDTO.getName();
if (fileFolder == null) {
return null;
}
//檔案上傳路徑更新為指定檔案資訊簽名后的臨時檔案夾,用于后期合并
path += "/" + fileFolder;
if (!this.createFileFolder(path, true)) {
return null;
}
//分片上傳,指定當前分片檔案的檔案名
String newFileName = String.valueOf(fileUploadDTO.getChunk());
return new java.io.File(path, newFileName);
}
/**
* 創建存放分片上傳的檔案的檔案夾
*
* @param file 檔案夾路徑
* @param hasTmp 是否有臨時檔案
* @return
*/
private boolean createFileFolder(String file, boolean hasTmp) {
//創建存放分片檔案的臨時檔案夾
java.io.File tmpFile = new java.io.File(file);
if (!tmpFile.exists()) {
try {
tmpFile.mkdirs();
} catch (SecurityException ex) {
log.error("無法創建檔案夾", ex);
return false;
}
}
if (hasTmp) {
//創建臨時檔案,用來記錄上傳分片檔案的修改時間,用于清理長期未完成的垃圾分片
tmpFile = new java.io.File(file + ".tmp");
if (tmpFile.exists()) {
return tmpFile.setLastModified(System.currentTimeMillis());
} else {
try {
tmpFile.createNewFile();
} catch (IOException ex) {
log.error("無法創建tmp檔案", ex);
return false;
}
}
}
return true;
}
}
第四步:修改Nacos配置中心的pd-file-server.yml檔案,加入storagePath配置項
5.10.3.3 介面測驗
第一步:啟動Nacos配置中心
第二步:啟動Nginx服務
第三步:啟動檔案服務
第四步:訪問分片上傳頁面,進行大檔案上傳
可以看到,上傳完成后,對應的分片上傳所需目錄、臨時檔案、分片檔案都已經創建成功了:


5.11 介面開發-分片合并
前面我們已經完成了分片上傳的介面,本小節需要完成的是將這些分片檔案合并為原始檔案并按照組態檔配置的存盤策略保存到相應位置,由于不同的存盤方式對應的分片合并方式也不同,所以我們需要提供不同的分片合并處理策略,具體介面設計如下:

5.11.1 FileChunkStrategy
FileChunkStrategy是分片檔案處理策略頂層介面,是對分片檔案處理的頂層抽象,具體代碼如下:
package com.itheima.pinda.file.strategy;
import com.itheima.pinda.base.R;
import com.itheima.pinda.file.dto.chunk.FileChunksMergeDTO;
import com.itheima.pinda.file.entity.File;
/**
* 檔案分片處理策略介面
*/
public interface FileChunkStrategy {
/**
* 分片合并
*
* @param merge
* @return
*/
R<File> chunksMerge(FileChunksMergeDTO merge);
}
5.11.2 AbstractFileChunkStrategy
AbstractFileChunkStrategy是抽象分片策略處理類,實作了FileChunkStrategy介面,AbstractFileChunkStrategy實作主要的分片合并處理流程,例如:分片臨時存盤路徑獲取、分片數量的檢查、合并后臨時分片檔案清理、合并后將檔案資訊保存到資料庫等,但是真正分片合并的處理程序需要其子類來完成,因為不同的存盤方案處理方式是不同的,
由于在進行分片合并處理程序中需要鎖,在資料中(檔案服務\資料\分片上傳\后端)已經提供了工具類,直接匯入專案使用即可,
AbstractFileChunkStrategy代碼如下:
package com.itheima.pinda.file.strategy.impl;
import com.itheima.pinda.base.R;
import com.itheima.pinda.file.dto.chunk.FileChunksMergeDTO;
import com.itheima.pinda.file.entity.File;
import com.itheima.pinda.file.enumeration.IconType;
import com.itheima.pinda.file.properties.FileServerProperties;
import com.itheima.pinda.file.service.FileService;
import com.itheima.pinda.file.strategy.FileChunkStrategy;
import com.itheima.pinda.file.utils.FileLock;
import com.itheima.pinda.file.utils.FileDataTypeUtil;
import com.itheima.pinda.utils.DateUtils;
import com.itheima.pinda.utils.NumberHelper;
import com.itheima.pinda.utils.StrPool;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.FileUtils;
import org.springframework.beans.factory.annotation.Autowired;
import java.io.IOException;
import java.nio.file.Paths;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.locks.Lock;
/**
* 檔案分片處理 抽象策略類
*/
@Slf4j
public abstract class AbstractFileChunkStrategy implements FileChunkStrategy {
@Autowired
protected FileService fileService;
@Autowired
protected FileServerProperties fileProperties;
protected FileServerProperties.Properties properties;
/**
* 分片合并
* @param info
* @return
*/
@Override
public R<File> chunksMerge(FileChunksMergeDTO info) {
// 570de89d476e6a5ba371f5fdd8d7920b.avi
String filename = new StringBuilder(info.getName()).append(StrPool.DOT).append(info.getExt()).toString();
//分片合并
R<File> result = chunksMerge(info, filename);
if (result.getIsSuccess() && result.getData() != null) {
//檔案名
File filePo = result.getData();
LocalDateTime now = LocalDateTime.now();
filePo.setDataType(FileDataTypeUtil.getDataType(info.getContextType()))
.setCreateMonth(DateUtils.formatAsYearMonthEn(now))
.setCreateWeek(DateUtils.formatAsYearWeekEn(now))
.setCreateDay(DateUtils.formatAsDateEn(now))
.setSubmittedFileName(info.getSubmittedFileName())
.setIsDelete(false)
.setSize(info.getSize())
.setFileMd5(info.getMd5())
.setContextType(info.getContextType())
.setFilename(filename)
.setExt(info.getExt())
.setIcon(IconType.getIcon(info.getExt()).getIcon());
//將上傳的檔案資訊保存到資料庫
fileService.save(filePo);
return R.success(filePo);
}
return result;
}
/**
* 分片合并
* @param info
* @param fileName
* @return
*/
private R<File> chunksMerge(FileChunksMergeDTO info, String fileName) {
//獲得分片檔案存盤的路徑 D:\\chunks\\2020\\05
String path = FileDataTypeUtil.getUploadPathPrefix(fileProperties.getStoragePath());
int chunks = info.getChunks();
String folder = info.getName();
String md5 = info.getMd5();
int chunksNum = this.getChunksNum(Paths.get(path, folder).toString());
//檢查是否滿足合并條件:分片數量是否足夠
if (chunks == chunksNum) {
//同步指定合并的物件
Lock lock = FileLock.getLock(folder);
try {
lock.lock();
//檢查是否滿足合并條件:分片數量是否足夠
List<java.io.File> files = new ArrayList<>(Arrays.asList(this.getChunks(Paths.get(path, folder).toString())));
if (chunks == files.size()) {
//按照名稱排序檔案,這里分片都是按照數字命名的
//這里存放的檔案名一定是數字
files.sort((f1, f2) -> NumberHelper.intValueOf0(f1.getName()) - NumberHelper.intValueOf0(f2.getName()));
R<File> result = merge(files, fileName, info);
//清理:檔案夾,tmp檔案
this.cleanSpace(folder, path);
return result;
}
} catch (Exception ex) {
log.error("資料分片合并失敗", ex);
return R.fail("資料分片合并失敗");
} finally {
//解鎖
lock.unlock();
//清理鎖物件
FileLock.removeLock(folder);
}
}
log.error("檔案[簽名:" + md5 + "]資料不完整,可能該檔案正在合并中");
return R.fail("資料不完整,可能該檔案正在合并中, 也有可能是上傳程序中某些分片丟失");
}
/**
* 子類實作具體的合并操作
*
* @param files 分片檔案
* @param fileName 唯一名 含后綴
* @param info 分片資訊
* @return
* @throws IOException
*/
protected abstract R<File> merge(List<java.io.File> files, String fileName, FileChunksMergeDTO info) throws IOException;
/**
* 清理分片上傳的相關資料
* 檔案夾,tmp檔案
*
* @param folder 檔案夾名稱
* @param path 上傳檔案根路徑
* @return
*/
protected boolean cleanSpace(String folder, String path) {
//洗掉分片檔案夾
java.io.File garbage = new java.io.File(Paths.get(path, folder).toString());
if (!FileUtils.deleteQuietly(garbage)) {
return false;
}
//洗掉tmp檔案
garbage = new java.io.File(Paths.get(path, folder + ".tmp").toString());
if (!FileUtils.deleteQuietly(garbage)) {
return false;
}
return true;
}
/**
* 獲取指定檔案的分片數量
*
* @param folder 檔案夾路徑
* @return
*/
private int getChunksNum(String folder) {
java.io.File[] filesList = this.getChunks(folder);
return filesList.length;
}
/**
* 獲取指定檔案的所有分片
*
* @param folder 檔案夾路徑
* @return
*/
private java.io.File[] getChunks(String folder) {
java.io.File targetFolder = new java.io.File(folder);
return targetFolder.listFiles((file) -> {
if (file.isDirectory()) {
return false;
}
return true;
});
}
}
5.11.3 LocalChunkServiceImpl
LocalChunkServiceImpl是AbstractFileChunkStrategy的子類,負責處理存盤策略為本地時的分片檔案合并操作,為了使程式能夠動態選擇具體的策略處理類,故將LocalChunkServiceImpl定義在LocalAutoConfigure配置類中,具體代碼如下:
/**
* 本地分片檔案處理策略類
*/
@Service
public class LocalChunkServiceImpl extends AbstractFileChunkStrategy {
/**
*分片合并
* @param files 分片檔案
* @param fileName 唯一名 含后綴
* @param info 分片資訊
* @return
* @throws IOException
*/
@Override
protected R<File> merge(List<java.io.File> files, String fileName, FileChunksMergeDTO info) throws IOException {
properties = fileProperties.getLocal();
//日期目錄
String relativePath = Paths.get(LocalDate.now().format(DateTimeFormatter.ofPattern(DateUtils.DEFAULT_MONTH_FORMAT_SLASH))).toString();
//合并后檔案的存盤路徑 例如:D:\\uploadFiles\\oss-file-service\\2020\\05
String path = Paths.get(properties.getEndpoint(), properties.getBucketName(), relativePath).toString();
//上傳檔案存放目錄,如果不存在則創建
java.io.File uploadFolder = new java.io.File(path);
if(!uploadFolder.exists()){
uploadFolder.mkdirs();
}
//創建合并后的檔案
java.io.File outputFile = new java.io.File(Paths.get(path, fileName).toString());
if (!outputFile.exists()) {
boolean newFile = outputFile.createNewFile();
if (!newFile) {
return R.fail("創建檔案失敗");
}
try (FileChannel outChannel = new FileOutputStream(outputFile).getChannel()) {
//同步nio 方式對分片進行合并, 有效的避免檔案過大導致記憶體溢位
for (java.io.File file : files) {
try (FileChannel inChannel = new FileInputStream(file).getChannel()) {
inChannel.transferTo(0, inChannel.size(), outChannel);
} catch (FileNotFoundException ex) {
log.error("檔案轉換失敗", ex);
return R.fail("檔案轉換失敗");
}
//洗掉分片
if (!file.delete()) {
log.error("分片[" + info.getName() + "=>" + file.getName() + "]洗掉失敗");
}
}
} catch (FileNotFoundException e) {
log.error("檔案輸出失敗", e);
return R.fail("檔案輸出失敗");
}
} else {
log.warn("檔案[{}], fileName={}已經存在", info.getName(), fileName);
}
String url = new StringBuilder(properties.getUriPrefix()).
append(bucketName).append(StrPool.SLASH).
append(relativePath).append(StrPool.SLASH).
append(fileName).toString();
File filePo = File.builder()
.relativePath(relativePath)
.url(StringUtils.replace(url, "\\", StrPool.SLASH))
.build();
return R.success(filePo);
}
}
5.11.4 FastDfsChunkServiceImpl
FastDfsChunkServiceImpl是AbstractFileChunkStrategy的子類,負責處理存盤策略為FastDFS時的分片檔案合并操作,為了使程式能夠動態選擇具體的策略處理類,故將FastDfsChunkServiceImpl定義在FastDfsAutoConfigure配置類中,具體代碼如下:
/**
* FastDfs分片檔案處理策略類
*/
@Service
public class FastDfsChunkServiceImpl extends AbstractFileChunkStrategy {
@Autowired
protected AppendFileStorageClient storageClient;
/**
* 分片合并
* @param files 分片檔案
* @param fileName 唯一名 含后綴
* @param info 分片資訊
* @return
* @throws IOException
*/
@Override
protected R<File> merge(List<java.io.File> files, String fileName, FileChunksMergeDTO info) throws IOException {
StorePath storePath = null;
for (int i = 0; i < files.size(); i++) {
java.io.File file = files.get(i);
FileInputStream in = FileUtils.openInputStream(file);
if (i == 0) {
storePath = storageClient.uploadAppenderFile(null, in,
file.length(), info.getExt());
} else {
storageClient.appendFile(storePath.getGroup(), storePath.getPath(),
in, file.length());
}
}
if (storePath == null) {
return R.fail("上傳失敗");
}
String url = new StringBuilder(fileProperties.getUriPrefix())
.append(storePath.getFullPath())
.toString();
File filePo = File.builder()
.url(url)
.group(storePath.getGroup())
.path(storePath.getPath())
.build();
return R.success(filePo);
}
}
5.11.5 AliChunkServiceImpl
AliChunkServiceImpl是AbstractFileChunkStrategy的子類,負責處理存盤策略為阿里云OSS時的分片檔案合并操作,為了使程式能夠動態選擇具體的策略處理類,故將AliChunkServiceImpl定義在AliOssAutoConfigure配置類中,具體代碼如下:
/**
* 阿里云OSS分片檔案處理策略類
*/
@Service
public class AliChunkServiceImpl extends AbstractFileChunkStrategy {
private OSS buildClient() {
properties = fileProperties.getAli();
return new OSSClientBuilder().build(properties.getEndpoint(), properties.getAccessKeyId(),
properties.getAccessKeySecret());
}
/**
* 分片合并
* @param files 分片檔案
* @param fileName 唯一名 含后綴
* @param info 分片資訊
* @return
* @throws IOException
*/
@Override
protected R<File> merge(List<java.io.File> files, String fileName, FileChunksMergeDTO info) throws IOException {
OSS client = buildClient();
String bucketName = properties.getBucketName();
//日期檔案夾
String relativePath = LocalDate.now().format(DateTimeFormatter.ofPattern(DEFAULT_MONTH_FORMAT_SLASH));
// web服務器存放的相對路徑
String relativeFileName = relativePath + StrPool.SLASH + fileName;
ObjectMetadata metadata = https://www.cnblogs.com/gitBook/p/new ObjectMetadata();
metadata.setContentDisposition("attachment;fileName=" + info.getSubmittedFileName());
metadata.setContentType(info.getContextType());
//步驟1:初始化一個分片上傳事件,
InitiateMultipartUploadRequest request = new InitiateMultipartUploadRequest(bucketName, relativeFileName, metadata);
InitiateMultipartUploadResult result = client.initiateMultipartUpload(request);
// 回傳uploadId,它是分片上傳事件的唯一標識,您可以根據這個ID來發起相關的操作,如取消分片上傳、查詢分片上傳等,
String uploadId = result.getUploadId();
// partETags是PartETag的集合,PartETag由分片的ETag和分片號組成,
List<PartETag> partETags = new ArrayList<PartETag>();
for (int i = 0; i < files.size(); i++) {
java.io.File file = files.get(i);
FileInputStream in = FileUtils.openInputStream(file);
UploadPartRequest uploadPartRequest = new UploadPartRequest();
uploadPartRequest.setBucketName(bucketName);
uploadPartRequest.setKey(relativeFileName);
uploadPartRequest.setUploadId(uploadId);
uploadPartRequest.setInputStream(in);
// 設定分片大小,除了最后一個分片沒有大小限制,其他的分片最小為100KB,
uploadPartRequest.setPartSize(file.length());
// 設定分片號,每一個上傳的分片都有一個分片號,取值范圍是1~10000,如果超出這個范圍,OSS將回傳InvalidArgument的錯誤碼,
uploadPartRequest.setPartNumber(i + 1);
// 每個分片不需要按順序上傳,甚至可以在不同客戶端上傳,OSS會按照分片號排序組成完整的檔案,
UploadPartResult uploadPartResult = client.uploadPart(uploadPartRequest);
// 每次上傳分片之后,OSS的回傳結果會包含一個PartETag,PartETag將被保存到partETags中,
partETags.add(uploadPartResult.getPartETag());
}
/* 步驟3:完成分片上傳, */
// 排序,partETags必須按分片號升序排列,
partETags.sort(Comparator.comparingInt(PartETag::getPartNumber));
// 在執行該操作時,需要提供所有有效的partETags,OSS收到提交的partETags后,會逐一驗證每個分片的有效性,當所有的資料分片驗證通過后,OSS將把這些分片組合成一個完整的檔案,
CompleteMultipartUploadRequest completeMultipartUploadRequest =
new CompleteMultipartUploadRequest(bucketName, relativeFileName, uploadId, partETags);
CompleteMultipartUploadResult uploadResult = client.completeMultipartUpload(completeMultipartUploadRequest);
String url = new StringBuilder(properties.getUriPrefix())
.append(relativePath)
.append(StrPool.SLASH)
.append(fileName)
.toString();
File filePo = File.builder()
.relativePath(relativePath)
.group(uploadResult.getETag())
.path(uploadResult.getRequestId())
.url(StringUtils.replace(url, "\\", StrPool.SLASH))
.build();
// 關閉OSSClient,
client.shutdown();
return R.success(filePo);
}
}
5.11.6 MinioChunkServiceImpl
MinioChunkServiceImpl是AbstractFileChunkStrategy的子類,負責處理存盤策略為MINIO時的分片檔案合并操作,為了使程式能夠動態選擇具體的策略處理類,故將MinioChunkServiceImpll定義在MinioAutoConfigure配置類中,具體代碼如下:
/**
* 分片檔案策略處理類
*/
@Service
public class MinioChunkServiceImpl extends AbstractFileChunkStrategy {
/**
* 分片合并抽象方法,需要子類實作
*
* @param files
* @param fileName
* @param fileChunksMergeDTO
* @return
*/
@Override
protected R<File> merge(List<java.io.File> files, String fileName, FileChunksMergeDTO fileChunksMergeDTO) throws Exception {
MinioAutoConfigure.this.buildClient(fileServerProperties);
Vector<InputStream> streams = new Vector<>();
//分片合并成功,需要封裝File物件相關屬性
File fileResult = new File();
for (java.io.File file : files) {//file對應的就是分片檔案
streams.add(new FileInputStream(file));
new FileInputStream(file).available();
//洗掉當前分片
file.delete();
}
//生成滿足要求的objectName和url
String objectName = doReName(fileName, fileResult);
//sequenceInputStream直接使用只能獲取第一個分片的資料,故先全部轉成輸出流再轉成輸入流
//存在問題:
//1.本身這個實作就不優雅
//2.OutOfMemoryError: Java heap space,測驗同時傳三個幾百M的檔案會發生記憶體溢位
//目前是分片檔案上傳到服務器,再程式里合并后再上傳到minio,下面提供了很多minio的工具類,可以改成分片檔案上傳到minio,利用minioClient合并檔案,目前未實作
try (SequenceInputStream sequenceInputStream = new SequenceInputStream(streams.elements());
ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) {
byte[] bytes = new byte[sequenceInputStream.available()];
int len;
while ((len = sequenceInputStream.read(bytes)) != -1) {
outputStream.write(bytes, 0, len);
}
byte[] outBytes = outputStream.toByteArray();
ByteBuffer buffer = ByteBuffer.wrap(outBytes);
try (ByteArrayInputStream inputStream = new ByteArrayInputStream(buffer.array())) {
// 使用putObject上傳一個檔案到存盤桶中
PutObjectArgs putObjectArgs = PutObjectArgs.builder()
.bucket(bucketName)
.object(objectName)
.contentType(fileChunksMergeDTO.getContextType())
.stream(inputStream, inputStream.available(), ObjectWriteArgs.MIN_MULTIPART_SIZE).build();
minioClient.putObject(putObjectArgs);
} catch (Exception ex) {
log.error("分片檔案合并失敗");
return R.fail("分片檔案合并失敗");
}
} catch (Exception ex) {
log.error("分片檔案合并失敗");
return R.fail("分片檔案合并失敗");
}
return R.success(fileResult);
}
}
5.11.7 分片合并介面
介面檔案:


在FileChunkController中提供分片合并方法,直接呼叫分片處理策略類完成分片合并操作:
@Autowired
private FileChunkStrategy fileChunkStrategy;//分片檔案處理策略
/**
* 分片合并
* @param info
* @return
*/
@ApiOperation(value = "https://www.cnblogs.com/gitBook/p/分片合并", notes = "所有分片上傳成功后,呼叫該介面對分片進行合并")
@PostMapping(value = "https://www.cnblogs.com/merge")
public R<File> saveChunksMerge(FileChunksMergeDTO info) {
log.info("info={}", info);
return fileChunkStrategy.chunksMerge(info);
}
第2-1-2章 傳統方式安裝FastDFS-附FastDFS常用命令
第2-1-3章 docker-compose安裝FastDFS,實作檔案存盤服務
第2-1-5章 docker安裝MinIO實作檔案存盤服務-springboot整合minio-minio全網最全的資料
全套代碼及資料全部完整提供,點此處下載
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/536929.html
標籤:Java
下一篇:第2-3-8章 分片上傳和分片合并的介面開發-檔案存盤服務系統-nginx/fastDFS/minio/阿里云oss/七牛云oss
