目錄
視頻上傳
接口一:檢查該視頻/媒資文件是否已經(jīng)上傳完成
接口二:檢查視頻分塊是否已經(jīng)在minio中已經(jīng)存在
接口三:上傳分塊文件到minio中(已經(jīng)上傳的分塊會在接口二進(jìn)行校驗(yàn))
接口四:合并上傳的分塊文件保存文件合并后的文件信息
視頻上傳
視頻上傳流程圖
接口一:檢查該視頻/媒資文件是否已經(jīng)上傳完成
首先:前端計(jì)算上傳視頻的MD5傳遞給后端,后端接收到MD5值之后,通過MD5到媒資管理表中查詢是否存在該文件信息,如果存在并且從媒資管理表中獲取這個(gè)文件存放在minio的位置,通過位置到minio分布式文件系統(tǒng)中查詢文件是否存在,如果mysql媒資信息表和minio中的媒資文件都存在返回true,無需進(jìn)行后續(xù)上傳,如果不存在返回前端false用來。
controller層
@ApiOperation(value = "文件上傳前檢查文件")
@PostMapping("/upload/checkfile")
public RestResponse<Boolean> checkfile(@RequestParam("fileMd5") String fileMd5) throws Exception {
RestResponse<Boolean> booleanRestResponse = mediaFilesService.checkFile(fileMd5);
return booleanRestResponse;
}
?service層
接口:
public RestResponse<Boolean> checkFile(String fileMd5);
實(shí)現(xiàn)類:
/**
* 通過md5值在數(shù)據(jù)庫判斷該文件是否存在
* @param fileMd5 文件的md5
* @return
*/
@Override
public RestResponse<Boolean> checkFile(String fileMd5) {
MediaFiles mediaFiles = baseMapper.selectById(fileMd5);
//說明在數(shù)據(jù)庫中已經(jīng)存在
if (mediaFiles!=null){
//檢查在minio中是否存在
//桶
String bucket = mediaFiles.getBucket();
//存儲目錄
String filePath = mediaFiles.getFilePath();
//文件流
InputStream stream = null;
try {
stream = minioClient.getObject(GetObjectArgs.builder().bucket(bucket).object(filePath).build());
if (stream!=null){
//return 文件已經(jīng)存在
return RestResponse.success(true);
}
} catch (Exception e) {
e.printStackTrace();
}
}
return RestResponse.success(false);
}
接口二:檢查視頻分塊是否已經(jīng)在minio中已經(jīng)存在
首先接收前端上傳媒資文件所生成的md5和分塊序號,通過文件的md5值和文件每一塊的序號組成在minio中存儲分塊文件的位置,獲取該位置的分塊文件是否存在,如果存在返回true(說明該分塊文件已經(jīng)在此前完成過上傳,無需再次上傳),如果不存在返回false,說明該分塊文件還未上傳,重新上傳這一塊文件。
controller層(控制層)
@ApiOperation(value = "分塊文件上傳前的檢測")
@PostMapping("/upload/checkchunk")
public RestResponse<Boolean> checkchunk(@RequestParam("fileMd5") String fileMd5, @RequestParam("chunk") int chunk) throws Exception {
RestResponse<Boolean> booleanRestResponse = mediaFilesService.checkChunk(fileMd5,chunk);
return booleanRestResponse;
}
?service層(業(yè)務(wù)邏輯層)
接口:
public RestResponse<Boolean> checkChunk(String fileMd5, int chunkIndex);
接口實(shí)現(xiàn)類:
/**
* 檢查分塊是否已經(jīng)存在
* @param fileMd5 文件的md5
* @param chunkIndex 分塊序號
* @return
*/
@Override
public RestResponse<Boolean> checkChunk(String fileMd5, int chunkIndex) {
//獲取所在的桶
String bucket = BUCKET_VIDEO;
//分塊文件的路徑
String chunkFileFolderPath = getChunkFileFolderPath(fileMd5) + chunkIndex;
//文件流
InputStream inputStream = null;
try {
inputStream = minioClient.getObject(GetObjectArgs.builder().bucket(bucket).object(chunkFileFolderPath).build());
} catch (Exception e) {
e.printStackTrace();
log.debug("分塊不存在");
}
if (inputStream !=null){
return RestResponse.success(true);
}
return RestResponse.success(false);
}
接口三:上傳分塊文件到minio中(已經(jīng)上傳的分塊會在接口二進(jìn)行校驗(yàn))
接收前端傳來的分割好的5M文件和這個(gè)文件的分割序號以及完整文件的md5值,將文件在本地轉(zhuǎn)儲一下,進(jìn)行上傳,如果是用戶原先上傳一半停止了上傳或者由于網(wǎng)絡(luò)波動(dòng)導(dǎo)致上傳中斷,下一次上傳原先文件時(shí)會在接口二中判斷原先文件的哪幾塊已經(jīng)存在minio中,存在的分塊無需再次上傳,不存在的分塊走上傳分塊文件接口進(jìn)行上傳分塊文件。
controller層(控制層)
@ApiOperation(value = "上傳分塊文件")
@PostMapping("/upload/uploadchunk")
public RestResponse uploadchunk(@RequestParam("file") MultipartFile file, @RequestParam("fileMd5") String fileMd5, @RequestParam("chunk") int chunk) throws Exception {
System.out.println(fileMd5);
System.out.println("分塊文件序號:"+chunk);
//創(chuàng)建臨時(shí)文件
File tempFile = File.createTempFile("minio", "temp");
//上傳的文件拷貝到臨時(shí)文件
file.transferTo(tempFile);
//文件路徑
String absolutePath = tempFile.getAbsolutePath();
RestResponse restResponse = mediaFilesService.uploadChunk(fileMd5, chunk, absolutePath);
return restResponse;
}
service(業(yè)務(wù)邏輯層)
接口:
/**
* @description 上傳分塊
* @param fileMd5 文件md5
* @param chunk 分塊序號
* @param localfChunkFilePath 本地文件路徑
*/
public RestResponse uploadChunk(String fileMd5,int chunk,String localfChunkFilePath);
實(shí)現(xiàn)類:
@Override
public RestResponse uploadChunk(String fileMd5, int chunk, String localfChunkFilePath) {
//得到分塊文件的路徑
String chunkFileFolderPath = getChunkFileFolderPath(fileMd5);
//
String chunkFilePath = chunkFileFolderPath + chunk;
//得到文件類型
String mimeType = getMimeType(null);
//上傳分塊文件到minio
boolean flag = false;
try {
flag = addMediaFilesToMinIO(localfChunkFilePath, mimeType, BUCKET_VIDEO, chunkFilePath);
} catch (Exception e) {
log.debug("上傳分塊文件:{},失敗:{}",chunkFilePath,e.getMessage());
}
if (!flag){
return RestResponse.validfail(false,"上傳分塊文件失敗");
}
return RestResponse.success(true);
}
上傳文件的方法
/**
* @description 將文件寫入minIO
* @param localFilePath 文件地址
* @param bucket 桶
* @param objectName 對象名稱
*/
public boolean addMediaFilesToMinIO(String localFilePath,String mimeType,String bucket, String objectName) {
try {
UploadObjectArgs testbucket = UploadObjectArgs.builder()
.bucket(bucket)
.object(objectName)
.filename(localFilePath)
.contentType(mimeType)
.build();
minioClient.uploadObject(testbucket);
log.info("上傳文件到minio成功,bucket:{},objectName:{}",bucket,objectName);
System.out.println("上傳成功");
return true;
} catch (Exception e) {
e.printStackTrace();
log.error("上傳文件到minio出錯(cuò),bucket:{},objectName:{},錯(cuò)誤原因:{}",bucket,objectName,e.getMessage(),e);
XueChengPlusException.cast("上傳文件到文件系統(tǒng)失敗");
}
return false;
}
接口四:合并上傳的分塊文件保存文件合并后的文件信息
當(dāng)前端按照5M將文件切分好依次上傳所有之后,會請求接口四(合并文件),后端接收原文件的md5值和上傳文件的名稱和分塊總數(shù),在業(yè)務(wù)層進(jìn)行處理,
1:首先會通過md5值獲取在minio中存儲該文件的分塊位置目錄
2:通過接收到分塊總數(shù)和存儲分塊的位置得到所有分塊文件的存儲的具體位置存放到集合中
3:通過位置路徑調(diào)用minio的合并api,將文件合并。
4:將合并好的文件臨時(shí)下載到本地獲得合并文件的md5值和前端上傳文件的md5進(jìn)行比較
5:刪除臨時(shí)本地的臨時(shí)文件,如果校驗(yàn)通過存儲該文件的相關(guān)信息到mysql媒資表中
6:就是將minio中存儲的分塊文件給刪除。
controller層
@ApiOperation(value = "合并文件")
@PostMapping("/upload/mergechunks")
public RestResponse mergechunks(@RequestParam("fileMd5") String fileMd5, @RequestParam("fileName") String fileName, @RequestParam("chunkTotal") int chunkTotal) throws Exception {
System.out.println(fileMd5);
System.out.println(chunkTotal);
Long companyId = 1232141425L;
UploadFileParamsDto uploadFileParamsDto = new UploadFileParamsDto();
uploadFileParamsDto.setFileType("001002");
uploadFileParamsDto.setTags("課程視頻");
uploadFileParamsDto.setRemark("");
uploadFileParamsDto.setFilename(fileName);
return mediaFilesService.mergechunks(companyId,fileMd5,chunkTotal,uploadFileParamsDto);
}
業(yè)務(wù)層
接口
/**
* @description 合并分塊
* @param companyId 機(jī)構(gòu)id
* @param fileMd5 文件md5
* @param chunkTotal 分塊總和
* @param uploadFileParamsDto 文件信息
*/
public RestResponse mergechunks(Long companyId,String fileMd5,int chunkTotal,UploadFileParamsDto uploadFileParamsDto);
實(shí)現(xiàn)類:
@Override
public RestResponse mergechunks(Long companyId, String fileMd5, int chunkTotal, UploadFileParamsDto uploadFileParamsDto) {
//=====獲取分塊文件路徑=====
String chunkFileFolderPath = getChunkFileFolderPath(fileMd5);
//組成將分塊文件路徑組成 List<ComposeSource>
List<ComposeSource> sourceObjectList = Stream.iterate(0, i -> ++i)
.limit(chunkTotal)
.map(i -> ComposeSource.builder()
.bucket(BUCKET_VIDEO)
.object(chunkFileFolderPath.concat(Integer.toString(i)))
.build())
.collect(Collectors.toList());
//=====合并=====
//文件名稱
String fileName = uploadFileParamsDto.getFilename();
//文件擴(kuò)展名
String extName = fileName.substring(fileName.lastIndexOf("."));
//合并文件路徑
String mergeFilePath = getFilePathByMd5(fileMd5, extName);
try {
//合并文件
ObjectWriteResponse response = minioClient.composeObject(
ComposeObjectArgs.builder()
.bucket(BUCKET_VIDEO)
.object(mergeFilePath)
.sources(sourceObjectList)
.build());
log.info("合并文件成功:{}",mergeFilePath);
} catch (Exception e) {
log.info("合并文件失敗,fileMd5:{},異常:{}",fileMd5,e.getMessage(),e);
return RestResponse.validfail(false, "合并文件失敗。");
}
// ====驗(yàn)證md5====
File minioFile = downloadFileFromMinIO(BUCKET_VIDEO,mergeFilePath);
if(minioFile == null){
log.debug("下載合并后文件失敗,mergeFilePath:{}",mergeFilePath);
return RestResponse.validfail(false, "下載合并后文件失敗。");
}
try (InputStream newFileInputStream = new FileInputStream(minioFile)) {
//minio上文件的md5值
String md5Hex = DigestUtils.md5Hex(newFileInputStream);
//比較md5值,不一致則說明文件不完整
if(!fileMd5.equals(md5Hex)){
return RestResponse.validfail(false, "文件合并校驗(yàn)失敗,最終上傳失敗。");
}
//文件大小
uploadFileParamsDto.setFileSize(minioFile.length());
}catch (Exception e){
log.debug("校驗(yàn)文件失敗,fileMd5:{},異常:{}",fileMd5,e.getMessage(),e);
return RestResponse.validfail(false, "文件合并校驗(yàn)失敗,最終上傳失敗。");
}finally {
if(minioFile!=null){
minioFile.delete();
}
}
//文件入庫
currentProxy.addMediaFilesToDb(companyId,fileMd5,uploadFileParamsDto,BUCKET_VIDEO,mergeFilePath);
//=====清除分塊文件=====
clearChunkFiles(chunkFileFolderPath,chunkTotal);
return RestResponse.success(true);
}
下載合并文件的方法文章來源:http://www.zghlxwxcb.cn/news/detail-579564.html
/**
* 從minio下載文件
* @param bucket 桶
* @param objectName 對象名稱
* @return 下載后的文件
*/
public File downloadFileFromMinIO(String bucket,String objectName){
//臨時(shí)文件
File minioFile = null;
FileOutputStream outputStream = null;
try{
InputStream stream = minioClient.getObject(GetObjectArgs.builder()
.bucket(bucket)
.object(objectName)
.build());
//創(chuàng)建臨時(shí)文件
minioFile=File.createTempFile("minio", ".merge");
outputStream = new FileOutputStream(minioFile);
IOUtils.copy(stream,outputStream);
return minioFile;
} catch (Exception e) {
e.printStackTrace();
}finally {
if(outputStream!=null){
try {
outputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return null;
}
清楚分塊文件的方法文章來源地址http://www.zghlxwxcb.cn/news/detail-579564.html
/**
* 清除分塊文件
* @param chunkFileFolderPath 分塊文件路徑
* @param chunkTotal 分塊文件總數(shù)
*/
/**
* 清除分塊文件
* @param chunkFileFolderPath 分塊文件路徑
* @param chunkTotal 分塊文件總數(shù)
*/
private void clearChunkFiles(String chunkFileFolderPath,int chunkTotal){
try {
List<DeleteObject> deleteObjects = Stream.iterate(0, i -> ++i)
.limit(chunkTotal)
.map(i -> new DeleteObject(chunkFileFolderPath.concat(Integer.toString(i))))
.collect(Collectors.toList());
RemoveObjectsArgs removeObjectsArgs = RemoveObjectsArgs.builder().bucket(BUCKET_VIDEO).objects(deleteObjects).build();
Iterable<Result<DeleteError>> results = minioClient.removeObjects(removeObjectsArgs);
results.forEach(r->{
DeleteError deleteError = null;
try {
deleteError = r.get();
} catch (Exception e) {
e.printStackTrace();
log.error("清楚分塊文件失敗,objectname:{}",deleteError.objectName(),e);
}
});
} catch (Exception e) {
e.printStackTrace();
log.error("清楚分塊文件失敗,chunkFileFolderPath:{}",chunkFileFolderPath,e);
}
}
到了這里,關(guān)于使用MinIO文件存儲系統(tǒng)【完成視頻斷點(diǎn)續(xù)傳】業(yè)務(wù)邏輯的文章就介紹完了。如果您還想了解更多內(nèi)容,請?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!