分布式文件系统MinIo
MinIO提供多个语言版本SDK的支持,下边找到java版本的文档:
地址:https://docs.min.io/docs/java-client-quickstart-guide.html
MinIO测试(上传、删除、下载)
public class MinioTest {
MinioClient minioClient =
MinioClient.builder()
.endpoint("http://192.168.101.65:9000")//对象存储服务的URL
//Access key就像用户ID,可以唯一标识你的账户。Secret key是你账户的密码。
.credentials("minioadmin", "minioadmin")
.build();
@Test
public void test_upload() throws Exception {
//通过扩展名得到媒体资源类型 mimeType
//根据扩展名取出mimeType
ContentInfo extensionMatch = ContentInfoUtil.findExtensionMatch(".mp4");
String mimeType = MediaType.APPLICATION_OCTET_STREAM_VALUE;//通用mimeType,字节流
if(extensionMatch!=null){
mimeType = extensionMatch.getMimeType();
}
//上传文件的参数信息
UploadObjectArgs uploadObjectArgs = UploadObjectArgs.builder()
.bucket("testbucket")//桶
.filename("D:\\develop\\upload\\1.mp4") //指定本地文件路径
.object("test/01/1.mp4")//对象名 放在子目录下
.contentType(mimeType)//设置媒体文件类型
.build();
//上传文件
minioClient.uploadObject(uploadObjectArgs);
}
//删除文件
@Test
public void test_delete() throws Exception {
//RemoveObjectArgs
RemoveObjectArgs removeObjectArgs = RemoveObjectArgs.builder().bucket("testbucket").object("1.mp4").build();
//删除文件
minioClient.removeObject(removeObjectArgs);
}
//查询文件 从minio中下载
@Test
public void test_getFile() throws Exception {
GetObjectArgs getObjectArgs = GetObjectArgs.builder().bucket("testbucket").object("test/01/1.mp4").build();
//查询远程服务获取到一个流对象
FilterInputStream inputStream = minioClient.getObject(getObjectArgs);
//指定输出流
FileOutputStream outputStream = new FileOutputStream(new File("D:\\develop\\upload\\1a.mp4"));
IOUtils.copy(inputStream,outputStream);
//校验文件的完整性对文件的内容进行md5
FileInputStream fileInputStream1 = new FileInputStream(new File("D:\\develop\\upload\\1.mp4"));
String source_md5 = DigestUtils.md5Hex(fileInputStream1);
FileInputStream fileInputStream = new FileInputStream(new File("D:\\develop\\upload\\1a.mp4"));
String local_md5 = DigestUtils.md5Hex(fileInputStream);
if(source_md5.equals(local_md5)){
System.out.println("下载成功");
}
}
}
上传视频-断点续传
流程如下:
1、前端上传前先把文件分成块
2、一块一块的上传,上传中断后重新上传,已上传的分块则不用再上传
3、各分块上传完成最后在服务端合并文件
视频分块合并测试
public class BigFileTest {
//分块测试
@Test
public void testChunk() throws IOException {
//源文件
File sourceFile = new File("D:\\develop\\upload\\1.项目背景.mp4");
//分块文件存储路径
String chunkFilePath = "D:\\develop\\upload\\chunk\\";
//分块文件大小
int chunkSize = 1024 * 1024 * 5;
//分块文件个数
int chunkNum = (int) Math.ceil(sourceFile.length() * 1.0 / chunkSize);
//使用流从源文件读数据,向分块文件中写数据
RandomAccessFile raf_r = new RandomAccessFile(sourceFile, "r");
//缓存区
byte[] bytes = new byte[1024];
for (int i = 0; i < chunkNum; i++) {
File chunkFile = new File(chunkFilePath + i);
//分块文件写入流
RandomAccessFile raf_rw = new RandomAccessFile(chunkFile, "rw");
int len = -1;
while ((len=raf_r.read(bytes))!=-1){
raf_rw.write(bytes,0,len);
if(chunkFile.length()>=chunkSize){
break;
}
}
raf_rw.close();
}
raf_r.close();
}
//将分块进行合并
@Test
public void testMerge() throws IOException {
//块文件目录
File chunkFolder = new File("D:\\develop\\upload\\chunk");
//源文件
File sourceFile = new File("D:\\develop\\upload\\1.项目背景.mp4");
//合并后的文件
File mergeFile = new File("D:\\develop\\upload\\1.项目背景_2.mp4");
//取出所有分块文件
File[] files = chunkFolder.listFiles();
//将数组转成list
List<File> filesList = Arrays.asList(files);
//对分块文件排序
Collections.sort(filesList, new Comparator<File>() {
@Override
public int compare(File o1, File o2) {
return Integer.parseInt(o1.getName())-Integer.parseInt(o2.getName());
}
});
//向合并文件写的流
RandomAccessFile raf_rw = new RandomAccessFile(mergeFile, "rw");
//缓存区
byte[] bytes = new byte[1024];
//遍历分块文件,向合并 的文件写
for (File file : filesList) {
//读分块的流
RandomAccessFile raf_r = new RandomAccessFile(file, "r");
int len = -1;
while ((len=raf_r.read(bytes))!=-1){
raf_rw.write(bytes,0,len);
}
raf_r.close();
}
raf_rw.close();
//合并文件完成后对合并的文件md5校验
FileInputStream fileInputStream_merge = new FileInputStream(mergeFile);
FileInputStream fileInputStream_source = new FileInputStream(sourceFile);
String md5_merge = DigestUtils.md5Hex(fileInputStream_merge);
String md5_source = DigestUtils.md5Hex(fileInputStream_source);
if(md5_merge.equals(md5_source)){
System.out.println("文件合并成功");
}
}
}
视频上传流程
1、前端对文件进行分块。
2、前端上传分块文件前请求媒资服务检查文件是否存在,如果已经存在则不再上传。
3、如果分块文件不存在则前端开始上传
4、前端请求媒资服务上传分块。
5、媒资服务将分块上传至MinIO。
6、前端将分块上传完毕请求媒资服务合并分块。
7、媒资服务判断分块上传完成则请求MinIO合并文件。
8、合并完成校验合并后的文件是否完整,如果不完整则删除文件。
MinIo分块上传合并视频测试
//将分块文件上传到minio
@Test
public void uploadChunk() throws IOException, ServerException, InsufficientDataException, ErrorResponseException, NoSuchAlgorithmException, InvalidKeyException, InvalidResponseException, XmlParserException, InternalException {
for (int i = 0; i < 6; i++) {
//上传文件的参数信息
UploadObjectArgs uploadObjectArgs = UploadObjectArgs.builder()
.bucket("testbucket")//桶
.filename("D:\\develop\\upload\\chunk\\"+i) //指定本地文件路径
.object("chunk/"+i)//对象名 放在子目录下
.build();
//上传文件
minioClient.uploadObject(uploadObjectArgs);
System.out.println("上传分块"+i+"成功");
}
}
//调用minio接口合并分块
@Test
public void testMerge() throws Exception {
// List<ComposeSource> sources = new ArrayList<>();
// for (int i = 0; i < 30; i++) {
// //指定分块文件的信息
// ComposeSource composeSource = ComposeSource.builder().bucket("testbucket").object("chunk/" + i).build();
// sources.add(composeSource);
// }
List<ComposeSource> sources = Stream.iterate(0, i -> ++i).limit(6).map(i -> ComposeSource.builder().bucket("testbucket").object("chunk/" + i).build()).collect(Collectors.toList());
//指定合并后的objectName等信息
ComposeObjectArgs composeObjectArgs = ComposeObjectArgs.builder()
.bucket("testbucket")
.object("merge01.mp4")
.sources(sources)//指定源文件
.build();
//合并文件,
//报错size 1048576 must be greater than 5242880,minio默认的分块文件大小为5M
minioClient.composeObject(composeObjectArgs);
}
//清除分块文件
@Test
public void test_removeObjects(){
//合并分块完成将分块文件清除
List<DeleteObject> deleteObjects = Stream.iterate(0, i -> ++i)
.limit(6)
.map(i -> new DeleteObject("chunk/".concat(Integer.toString(i))))
.collect(Collectors.toList());
RemoveObjectsArgs removeObjectsArgs = RemoveObjectsArgs.builder().bucket("testbucket").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();
}
});
}
视频转码(avi转成mp4)
使用FFmpeg对视频进行编码
//ffmpeg的路径
String ffmpeg_path = "D:\\soft\\ffmpeg\\ffmpeg.exe";//ffmpeg的安装位置
//源avi视频的路径
String video_path = "D:\\develop\\upload\\02-概述-分库分表是什么.avi";
//转换后mp4文件的名称
String mp4_name = "02-概述-分库分表是什么.mp4";
//转换后mp4文件的路径
String mp4_path = "D:\\develop\\upload\\02-概述-分库分表是什么.mp4";
//创建工具类对象
Mp4VideoUtil videoUtil = new Mp4VideoUtil(ffmpeg_path,video_path,mp4_name,mp4_path);
//开始视频转换,成功将返回success
String s = videoUtil.generateMp4();
System.out.println(s);