下载minio

官网:min.io

下载minio,下载地址在dl.min.io/server/mini…

minio特色

MinIO构建散布式文件体系,MinIO 是一个十分轻量的服务,能够很简略的和其他应用的结合运用,它兼容亚马逊 S3 云存储服务接口,十分适合于存储大容量非结构化的数据,例如图片、视频、日志文件、备份数据和容器/虚拟机镜像等。

它一大特色就是轻量,运用简略,功能强大,支撑各种渠道,单个文件最大5TB,兼容 Amazon S3接口,供给了 Java、Python、GO等多版别SDK支撑。

MinIO集群采用去中心化共享架构,每个结点是对等关系,经过Nginx可对MinIO进行负载均衡拜访。

去中心化有什么优点?

在大数据范畴,一般的规划理念都是无中心和散布式。Minio散布式模式能够协助你建立一个高可用的目标存储服务,你能够运用这些存储设备,而不用考虑其实在物理方位。

它将散布在不同服务器上的多块硬盘组成一个目标存储服务。由于硬盘散布在不同的节点上,散布式Minio避免了单点故障。如下图

Java构建minio文件系统实现分片上传

Minio运用纠删码技能来保护数据,它是一种康复丢掉和损坏数据的数学算法,它将数据分块冗余的涣散存储在各各节点的磁盘上,一切的可用磁盘组成一个集合,上图由8块硬盘组成一个集合,当上传一个文件时会经过纠删码算法核算对文件进行分块存储,除了将文件本身分红4个数据块,还会生成4个校验块,数据块和校验块会涣散的存储在这8块硬盘上。

运用纠删码的优点是即使丢掉一半数量(N/2)的硬盘,仍然能够康复数据。 比方上边集合中有4个以内的硬盘损害仍可保证数据康复,不影响上传和下载,如果多于一半的硬盘坏了则无法康复。

发动minio

需要去官网下载 minio,下载完后创立 data 文件夹(数量随意),如下创立了四个,执行命令运行

minio.exe server D:\minio\data1 D:\minio\data2 D:\minio\data3 D:\minio\data4

Java构建minio文件系统实现分片上传

运行后拜访 9000 端口,输入用户、暗码,默以为 minioadmin,创立 buckets ,并把 buckets 设为 public

Java构建minio文件系统实现分片上传

Java构建minio文件系统实现分片上传

Java操作minio

安装 Java 依赖

<!-- minio -->
<dependency>
    <groupId>io.minio</groupId>
    <artifactId>minio</artifactId>
    <version>8.4.3</version>
</dependency>
<dependency>
    <groupId>com.squareup.okhttp3</groupId>
    <artifactId>okhttp</artifactId>
    <version>4.8.1</version>
</dependency>
<!--依据扩展名取mimetype-->
<dependency>
    <groupId>com.j256.simplemagic</groupId>
    <artifactId>simplemagic</artifactId>
    <version>1.17</version>
</dependency>

上传、删去、下载文件测验

创立测验类测验

import com.j256.simplemagic.ContentInfo;
import com.j256.simplemagic.ContentInfoUtil;
import io.minio.GetObjectArgs;
import io.minio.MinioClient;
import io.minio.RemoveObjectArgs;
import io.minio.UploadObjectArgs;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.io.IOUtils;
import org.junit.jupiter.api.Test;
import org.springframework.http.MediaType;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FilterInputStream;
/**
 * @description 测验minio的sdk
 */
public class MinioTest {
    // 获取minio目标
    MinioClient minioClient =
            MinioClient.builder()
                    .endpoint("http://minio运行的ip地址:9000")
                    .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("C:\...\electron.png") // 指定本地上传的文件途径
                .object("test1.png") // 上传后文件的称号 如果是test/test.png 会放在test目录下
                .contentType(mimeType)//设置媒体文件类型
                .build();
        // 上传文件
        minioClient.uploadObject(uploadObjectArgs);
        // todo: 校验文件的完整性
    }
    /**
     * 删去文件
     */
    @Test
    public void test_delete() throws Exception {
        // 要删去文件的参数信息
        RemoveObjectArgs removeObjectArgs = RemoveObjectArgs.builder()
                .bucket("testbucket")
                .object("test1.png")
                .build();
        // 删去文件
        minioClient.removeObject(removeObjectArgs);
    }
    /**
     * 查询文件下载 从minio中下载
     */
    @Test
    public void test_getFile() throws Exception {
        // 需要获取的文件的参数信息
        GetObjectArgs getObjectArgs = GetObjectArgs.builder()
                .bucket("testbucket")
                .object("test1.png")
                .build();
        // 获取文件输入流
        FilterInputStream inputStream = minioClient.getObject(getObjectArgs);
        // 指定输出流
        FileOutputStream outputStream = new FileOutputStream(new File("D:\upload\test1.png"));
        // 把输入流拷贝给输出流
        IOUtils.copy(inputStream, outputStream);
        // 经过md5 校验文件的完整性
        String source_md5 = DigestUtils.md5Hex(inputStream);
        // 获取下载的文件的输入流
        FileInputStream fileInputStream = new FileInputStream(new File("D:\upload\test1.png"));
        String local_md5 = DigestUtils.md5Hex(fileInputStream);
        if (source_md5.equals(local_md5)) {
            System.out.println("下载成功");
        }
    }
}

上传成功示例

Java构建minio文件系统实现分片上传

开发上传图片、pdf、文档接口

1.装备上传文件巨细

Java构建minio文件系统实现分片上传

# 设置最大上传文件的巨细为10MB
spring.servlet.multipart.max-file-size=10MB
# 设置单个文件最大上传巨细为10MB
spring.servlet.multipart.max-request-size=10MB

2.装备 minio 的信息,发动类的 application.yml

Java构建minio文件系统实现分片上传

## minio装备
minio:
  endpoint: http://ip地址:9000
  accessKey: minioadmin
  secretKey: minioadmin
  bucket:
    files: mediafiles
    videofiles: videofiles

3.装备 config

Java构建minio文件系统实现分片上传

package com.xuecheng.media.config;
import io.minio.MinioClient;
import lombok.Data;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
@Data
public class MinioConfig {
    @Value("${minio.endpoint}")
    private String endpoint;
    @Value("${minio.accessKey}")
    private String accessKey;
    @Value("${minio.secretKey}")
    private String secretKey;
    @Bean
    public MinioClient minioClient() {
        MinioClient minioClient =
                MinioClient.builder()
                        .endpoint(endpoint)
                        .credentials(accessKey, secretKey)
                        .build();
        return minioClient;
    }
}

4.control

import com.xuecheng.media.model.dto.QueryMediaParamsDto;
import com.xuecheng.media.model.dto.UploadFileParamsDto;
import com.xuecheng.media.model.dto.UploadFileResultDto;
import com.xuecheng.media.model.po.MediaFiles;
import com.xuecheng.media.service.MediaFileService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import java.io.File;
import java.io.IOException;
/**
 * @description 媒资文件办理接口
 */
@RestController
public class MediaFilesController {
    @Autowired
    MediaFileService mediaFileService;
    // consumes指定上传类型
    // @RequestPart("filedata")指定上传称号
    @RequestMapping(value = "/upload/coursefile", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
    public UploadFileResultDto upload(@RequestPart("file") MultipartFile file) throws IOException {
        // 1.映射dto
        UploadFileParamsDto uploadFileParamsDto = new UploadFileParamsDto();
        // 文件称号
        uploadFileParamsDto.setFilename(file.getOriginalFilename());
        // 文件巨细
        uploadFileParamsDto.setFileSize(file.getSize());
        // 文件类型
        uploadFileParamsDto.setFileType("001001");
        // 2.创立一个临时文件 以获取文件途径
        File tempFile = File.createTempFile("minio", "temp");
        file.transferTo(tempFile);
        // 取出文件的绝对途径
        String absolutePath = tempFile.getAbsolutePath();
        Long companyId = 1232141425L;
        // 3.调用service的上传接口
        return mediaFileService.uploadFile(companyId, uploadFileParamsDto, absolutePath);
    }
}

5.service

import com.xuecheng.media.model.dto.QueryMediaParamsDto;
import com.xuecheng.media.model.dto.UploadFileParamsDto;
import com.xuecheng.media.model.dto.UploadFileResultDto;
import com.xuecheng.media.model.po.MediaFiles;
public interface MediaFileService {
    /**
     * 上传文件
     *
     * @param companyId           组织id
     * @param uploadFileParamsDto 文件信息
     * @param localFilePath       文件本地途径
     * @return UploadFileResultDto
     */
    public UploadFileResultDto uploadFile(Long companyId, UploadFileParamsDto uploadFileParamsDto, String localFilePath);
    public MediaFiles addMediaFilesToDb(Long companyId, String fileMd5, UploadFileParamsDto uploadFileParamsDto, String bucket, String objectName);
}

6.impl

import com.j256.simplemagic.ContentInfo;
import com.j256.simplemagic.ContentInfoUtil;
import com.xuecheng.base.exception.XueChengPlusException;
import com.xuecheng.base.model.PageParams;
import com.xuecheng.base.model.PageResult;
import com.xuecheng.media.mapper.MediaFilesMapper;
import com.xuecheng.media.model.dto.QueryMediaParamsDto;
import com.xuecheng.media.model.dto.UploadFileParamsDto;
import com.xuecheng.media.model.dto.UploadFileResultDto;
import com.xuecheng.media.model.po.MediaFiles;
import com.xuecheng.media.service.MediaFileService;
import io.minio.MinioClient;
import io.minio.UploadObjectArgs;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.digest.DigestUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.io.File;
import java.io.FileInputStream;
import java.text.SimpleDateFormat;
import java.time.LocalDateTime;
import java.util.Date;
import java.util.List;
@Slf4j
@Service
public class MediaFileServiceImpl implements MediaFileService {
    @Autowired
    MediaFilesMapper mediaFilesMapper;
    @Autowired
    MinioClient minioClient;
    @Autowired
    MediaFileService currentProxy;
    // 获取桶称号
    @Value("${minio.bucket.files}")
    private String bucket_mediafiles;
    // 依据扩展名获取mimeType
    private String getMimeType(String extension) {
        if (extension == null) {
            extension = "";
        }
        //依据扩展名取出mimeType
        ContentInfo extensionMatch = ContentInfoUtil.findExtensionMatch(extension);
        String mimeType = MediaType.APPLICATION_OCTET_STREAM_VALUE;//通用mimeType,字节省
        if (extensionMatch != null) {
            mimeType = extensionMatch.getMimeType();
        }
        return mimeType;
    }
    /**
     * 将文件上传到minio
     *
     * @param localFilePath 文件本地途径
     * @param mimeType      媒体类型
     * @param bucket        桶
     * @param objectName    文件称号
     * @return
     */
    public boolean addMediaFilesToMinIO(String localFilePath, String mimeType, String bucket, String objectName) {
        try {
            UploadObjectArgs uploadObjectArgs = UploadObjectArgs.builder()
                    .bucket(bucket)//桶
                    .filename(localFilePath) //指定本地文件途径
                    .object(objectName)// 文件称号 也能够放在子目录下
                    .contentType(mimeType)//设置媒体文件类型
                    .build();
            //上传文件
            minioClient.uploadObject(uploadObjectArgs);
            log.debug("上传文件到minio成功,bucket:{},objectName:{},错误信息:{}", bucket, objectName);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            log.error("上传文件犯错,bucket:{},objectName:{},错误信息:{}", bucket, objectName, e.getMessage());
        }
        return false;
    }
    // 获取文件默许存储目录途径 年/月/日
    private String getDefaultFolderPath() {
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
        String folder = sdf.format(new Date()).replace("-", "/") + "/";
        return folder;
    }
    // 获取文件的md5
    private String getFileMd5(File file) {
        try (FileInputStream fileInputStream = new FileInputStream(file)) {
            String fileMd5 = DigestUtils.md5Hex(fileInputStream);
            return fileMd5;
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }
    // 上传文件
    @Override
    public UploadFileResultDto uploadFile(Long companyId, UploadFileParamsDto uploadFileParamsDto, String localFilePath) {
        // 1.文件名
        String filename = uploadFileParamsDto.getFilename();
        // 2.先得到扩展名
        String extension = filename.substring(filename.lastIndexOf("."));
        // 3.经过扩展名得到mimeType
        String mimeType = getMimeType(extension);
        // 4.子目录
        String defaultFolderPath = getDefaultFolderPath();
        // 5.文件的md5值
        String fileMd5 = getFileMd5(new File(localFilePath));
        String objectName = defaultFolderPath + fileMd5 + extension;
        // 6.上传文件到minio
        boolean result = addMediaFilesToMinIO(localFilePath, mimeType, bucket_mediafiles, objectName);
        if (!result) {
            myException.cast("上传文件失利");
        }
        // 7.将文件信息保存到数据库
        MediaFiles mediaFiles = currentProxy.addMediaFilesToDb(companyId, fileMd5, uploadFileParamsDto, bucket_mediafiles, objectName);
        if (mediaFiles == null) {
            myException.cast("上传文件后保存信息入库失利");
        }
        // 8.映射回来目标
        UploadFileResultDto uploadFileResultDto = new UploadFileResultDto();
        BeanUtils.copyProperties(mediaFiles, uploadFileResultDto);
        return uploadFileResultDto;
    }
    @Transactional
    public MediaFiles addMediaFilesToDb(Long companyId, String fileMd5, UploadFileParamsDto uploadFileParamsDto, String bucket, String objectName) {
        //将文件信息保存到数据库
        MediaFiles mediaFiles = mediaFilesMapper.selectById(fileMd5);
        if (mediaFiles == null) {
            mediaFiles = new MediaFiles();
            ...映射po数据
            //刺进数据库
            int insert = mediaFilesMapper.insert(mediaFiles);
            if (insert <= 0) {
                log.debug("向数据库保存文件失利,bucket:{},objectName:{}", bucket, objectName);
                return null;
            }
            return mediaFiles;
        }
        return mediaFiles;
    }
}

分片上传

Java开发分片上传接口

  1. 界说测验表
create table test_minio.media_files
(
    id        varchar(32)   not null comment '主键'
        primary key,
    filename  varchar(255)  null comment '文件称号',
    file_type varchar(12)   null comment '文件类型 (图片、文档、视频)',
    file_id   varchar(32)   null comment '文件id',
    file_path varchar(512)  null comment '文件存储目录',
    url       varchar(1024) null comment '文件拜访地址',
    file_size bigint        null comment '文件巨细'
)
    comment '文件信息表';

2.界说 dto , po 就不展现了

package com.minio.model.dto;
import lombok.Data;
import lombok.ToString;
@Data
@ToString
public class UploadFileParamsDto {
    /**
     * 文件称号
     */
    private String filename;
    /**
     * 文件类型(文档,音频,视频)
     */
    private String fileType;
    /**
     * 文件巨细
     */
    private Long fileSize;
}

3.controller

package com.minio.controller;
import com.minio.model.dto.R;
import com.minio.model.dto.UploadFileParamsDto;
import com.minio.model.po.MediaFiles;
import com.minio.service.MediaFilesService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import java.io.File;
import java.util.List;
@RestController
public class MinioController {
    @Autowired
    MediaFilesService mediaFilesService;
    /**
     * 获取悉数文件
     */
    @GetMapping("/files")
    public R<List<MediaFiles>> getFiles() {
        List<MediaFiles> files = mediaFilesService.getFiles();
        return R.success(files);
    }
    /**
     * 直接上传文件 不触及分块
     *
     * @param file
     * @return
     * @throws Exception
     */
    @PostMapping("/upload/files")
    // @RequestPart("file") 注解表示将 HTTP 请求中的一个文件作为请求参数传递给方法,并绑定到对应的方法参数上。
    public R<MediaFiles> uplaodFile(@RequestPart("file") MultipartFile file) throws Exception {
        // 1.映射dto
        UploadFileParamsDto uploadFileParamsDto = new UploadFileParamsDto();
        // 文件称号
        uploadFileParamsDto.setFilename(file.getOriginalFilename());
        // 文件巨细
        uploadFileParamsDto.setFileSize(file.getSize());
        // 文件类型
        uploadFileParamsDto.setFileType("图片");
        // 2.创立一个临时文件 以获取文件途径
        File tempFile = File.createTempFile("minio", "temp");
        file.transferTo(tempFile);
        // 取出文件的绝对途径
        String absolutePath = tempFile.getAbsolutePath();
        // 3.调用service
        MediaFiles mediaFiles = mediaFilesService.uploadFile(uploadFileParamsDto, absolutePath);
        return R.success(mediaFiles);
    }
    /**
     * 上传分块
     *
     * @param fileMd5    原始文件的md5
     * @param chunk      分块文件
     * @param chunkIndex 分块序号
     * @return
     * @throws Exception
     */
    @PostMapping("/upload/uploadchunk")
    public R<Boolean> uploadChunk(@RequestParam("fileMd5") String fileMd5, @RequestParam("chunk") MultipartFile chunk, @RequestParam("chunkIndex") int chunkIndex) throws Exception {
        // 1.创立临时文件获取文件途径
        File tempFile = File.createTempFile("minio", "temp");
        chunk.transferTo(tempFile);
        // 取出文件的绝对途径
        String localFilePath = tempFile.getAbsolutePath();
        // 3.调用service上传分块
        Boolean result = mediaFilesService.uploadChunk(fileMd5, chunkIndex, localFilePath);
        return R.success(result);
    }
    /**
     * 查看分块
     *
     * @param fileMd5    原始文件的md5
     * @param chunkIndex 分块序号
     */
    @PostMapping("/upload/checkchunk")
    public R<Boolean> checkChunk(@RequestParam("fileMd5") String fileMd5, @RequestParam("chunkIndex") int chunkIndex) {
        Boolean result = mediaFilesService.checkChunk(fileMd5, chunkIndex);
        return R.success(result);
    }
    /**
     * 兼并文件
     *
     * @param fileMd5    原始文件的md5
     * @param fileName   原始文件的称号
     * @param chunkTotal 分块总数
     */
    @PostMapping("/upload/mergechunks")
    public R<Boolean> mergeChunk(@RequestParam("fileMd5") String fileMd5, @RequestParam("fileName") String fileName, @RequestParam("chunkTotal") int chunkTotal) {
        // 1.映射文件参数信息
        UploadFileParamsDto uploadFileParamsDto = new UploadFileParamsDto();
        uploadFileParamsDto.setFilename(fileName);
        uploadFileParamsDto.setFileType("视频");
        Boolean res = mediaFilesService.mergeChunk(fileMd5, chunkTotal, uploadFileParamsDto);
        return R.success(res);
    }
}

4.封装东西类 Utils

package com.minio.utils;
import com.j256.simplemagic.ContentInfo;
import com.j256.simplemagic.ContentInfoUtil;
import org.apache.commons.codec.digest.DigestUtils;
import org.springframework.http.MediaType;
import java.io.File;
import java.io.FileInputStream;
import java.text.SimpleDateFormat;
import java.util.Date;
public class Utils {
    // 依据扩展名取出mimeType
    public static String getMimeType(String extension) {
        if (extension == null) {
            extension = "";
        }
        ContentInfo extensionMatch = ContentInfoUtil.findExtensionMatch(extension);
        String mimeType = MediaType.APPLICATION_OCTET_STREAM_VALUE;//通用mimeType,字节省
        if (extensionMatch != null) {
            mimeType = extensionMatch.getMimeType();
        }
        return mimeType;
    }
    //格式化年/月/日 用来作为文件存储目录 分片不用这个 分片用md5
    public static String getFormatTime() {
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
        String folder = sdf.format(new Date()).replace("-", "/") + "/";
        return folder;
    }
    // md5加密文件
    public static String getFileMd5(File file) {
        try (FileInputStream fileInputStream = new FileInputStream(file)) {
            String fileMd5 = DigestUtils.md5Hex(fileInputStream);
            return fileMd5;
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }
}

5.service

package com.minio.service;
import com.minio.model.dto.UploadFileParamsDto;
import com.minio.model.po.MediaFiles;
import java.util.List;
public interface MediaFilesService {
    /**
     * 获取悉数文件信息
     */
    List<MediaFiles> getFiles();
    /**
     * 上传文件
     *
     * @param uploadFileParamsDto 文件信息
     * @param localFilePath       本地文件途径
     */
    MediaFiles uploadFile(UploadFileParamsDto uploadFileParamsDto, String localFilePath);
    /**
     * 上传分块
     *
     * @param fileMd5            原始文件的md5
     * @param chunkIndex         分块序号
     * @param localChunkFIlePath 分块文件本地途径
     * @return
     */
    Boolean uploadChunk(String fileMd5, int chunkIndex, String localChunkFIlePath);
    /**
     * 查看分块
     *
     * @param fileMd5    原始文件md5
     * @param chunkIndex 分块序号
     */
    Boolean checkChunk(String fileMd5, int chunkIndex);
    /**
     * 兼并分块
     *
     * @param fileMd5             原始文件md5 用来获取分块目录
     * @param chunkTotal          分块总数
     * @param uploadFileParamsDto 文件参数
     * @return
     */
    Boolean mergeChunk(String fileMd5, int chunkTotal, UploadFileParamsDto uploadFileParamsDto);
    /**
     * 文件信息入库
     *
     * @param fileMd5             原始文件md5 用来做文件的id
     * @param uploadFileParamsDto 文件参数
     * @param bucket              存储的桶
     * @param filePath            文件存储途径
     * @return
     */
    MediaFiles addFilesToDb(String fileMd5, UploadFileParamsDto uploadFileParamsDto, String bucket, String filePath);
}

6.impl

package com.minio.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.minio.exception.Code;
import com.minio.exception.MyException;
import com.minio.mapper.MediaFilesMapper;
import com.minio.model.dto.UploadFileParamsDto;
import com.minio.model.po.MediaFiles;
import com.minio.service.MediaFilesService;
import com.minio.utils.Utils;
import io.minio.*;
import io.minio.messages.DeleteError;
import io.minio.messages.DeleteObject;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.io.File;
import java.io.FilterInputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
@Slf4j
@Service
public class MediaFilesServiceImpl implements MediaFilesService {
    @Autowired
    MediaFilesMapper mediaFilesMapper;
    @Autowired
    MinioClient minioClient;
    @Autowired
    MediaFilesService currentProxy;
    //存储一般文件
    @Value("${minio.bucket.files}")
    private String bucket_files;
    //存储视频
    @Value("${minio.bucket.video}")
    private String bucket_video;
    @Override
    public List<MediaFiles> getFiles() {
        LambdaQueryWrapper<MediaFiles> wrapper = new LambdaQueryWrapper<>();
        return mediaFilesMapper.selectList(wrapper);
    }
    /**
     * 上传文件
     *
     * @param uploadFileParamsDto 文件信息
     * @param localFilePath       本地文件途径
     */
    @Override
    public MediaFiles uploadFile(UploadFileParamsDto uploadFileParamsDto, String localFilePath) {
        // 1.获取文件名
        String filename = uploadFileParamsDto.getFilename();
        // 2.获取扩展名
        // lastIndexOf() 方法查找字符串中最后一个出现指定字符的方位,并回来该方位到字符串结束的子字符串
        String extension = filename.substring(filename.lastIndexOf("."));
        // 3.经过扩展名拿到mimeType
        String mimeType = Utils.getMimeType(extension);
        // 4.装备存储文件在minio的目录 年/月/日
        String formatTime = Utils.getFormatTime();
        // 5.获取文件的md5值作为存储在minio的文件称号
        String fileMd5 = Utils.getFileMd5(new File(localFilePath));
        // 6.装备文件存储途径 目录(年/月/日)+称号+后缀名
        String filePath = formatTime + fileMd5 + extension;
        // 7.上传到minio
        boolean result = uploadFilesToMinIO(localFilePath, mimeType, bucket_files, filePath);
        if (!result) {
            MyException.cast(Code.UPLOAD_ERROR);
        }
        // 8.将数据保存到数据库
        MediaFiles mediaFiles = currentProxy.addFilesToDb(fileMd5, uploadFileParamsDto, bucket_files, filePath);
        if (mediaFiles == null) {
            MyException.cast(Code.SERVICE_BUSY);
        }
        return mediaFiles;
    }
    /**
     * 上传分块
     *
     * @param fileMd5            原始文件的md5
     * @param chunkIndex         分块序号
     * @param localChunkFIlePath 分块文件本地途径
     * @return
     */
    @Override
    public Boolean uploadChunk(String fileMd5, int chunkIndex, String localChunkFIlePath) {
        // 1.依据md5获取文件存储途径 MD5的前面两个字符作为目录 如 a/b/abcxxxxxxxx/chunk/i
        String chunkFilePath = getChunkFilePath(fileMd5) + chunkIndex;
        // 2.获取mimeType
        String mimeType = Utils.getMimeType(null);
        // 3.将分块文件上传到minio
        boolean b = uploadFilesToMinIO(localChunkFIlePath, mimeType, bucket_video, chunkFilePath);
        if (!b) {
            MyException.cast(Code.UPLOAD_ERROR);
            return false;
        }
        return true;
    }
    /**
     * 查看分块
     *
     * @param fileMd5    原始文件md5
     * @param chunkIndex 分块序号
     * @return
     */
    @Override
    public Boolean checkChunk(String fileMd5, int chunkIndex) {
        // 1.获取minio存储分块的目录途径
        String chunkFilePath = getChunkFilePath(fileMd5);
        // 2.获取文件参数信息
        GetObjectArgs getObjectArgs = GetObjectArgs.builder()
                .bucket(bucket_video)
                .object(chunkFilePath + chunkIndex)
                .build();
        // 3.查看minio中分块是否存在
        try {
            FilterInputStream inputStream = minioClient.getObject(getObjectArgs);
            if (inputStream != null) {
                return true;
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return false;
    }
    /**
     * 兼并分块
     *
     * @param fileMd5             原始文件md5 用来获取分块目录
     * @param chunkTotal          分块总数
     * @param uploadFileParamsDto 文件参数
     * @return
     */
    @Override
    public Boolean mergeChunk(String fileMd5, int chunkTotal, UploadFileParamsDto uploadFileParamsDto) {
        // 1.获取悉数分块文件的信息
        List<ComposeSource> sources = new ArrayList<>();
        // 获取分块目录
        String chunkFilePath = getChunkFilePath(fileMd5);
        for (int i = 0; i < chunkTotal; i++) {
            // 指定分块文件的信息
            ComposeSource composeSource = ComposeSource.builder()
                    .bucket(bucket_video)
                    .object(chunkFilePath + i)
                    .build();
            sources.add(composeSource);
        }
        // 2.兼并分块
        // 2.1 获取源文件称号作为
        String filename = uploadFileParamsDto.getFilename();
        // 2.2 获取扩展名
        String extension = filename.substring(filename.lastIndexOf("."));
        // 2.3 获取兼并后的文件途径
        String objectName = fileMd5.substring(0, 1) + "/" + fileMd5.substring(1, 2) + "/" + fileMd5 + "/" + fileMd5 + extension;
        // 2.4 界说兼并参数
        ComposeObjectArgs composeObjectArgs = ComposeObjectArgs.builder()
                .bucket(bucket_video)
                .object(objectName) // 兼并后文件称号
                .sources(sources) // 指定源文件
                .build();
        // 2.5 兼并文件 minio默许的分块巨细为5M
        try {
            minioClient.composeObject(composeObjectArgs);
        } catch (Exception e) {
            e.printStackTrace();
            log.error("兼并文件犯错,bucket:{},mergeName:{},错误信息:{}", bucket_video, objectName, e.getMessage());
            MyException.cast(Code.SERVICE_BUSY);
            return false;
        }
        // todo: 3.校验兼并后的源文件是否共同,视频上传才成功 如何校验
        // 4.将文件信息入库
        MediaFiles mediaFiles = currentProxy.addFilesToDb(fileMd5, uploadFileParamsDto, bucket_video, objectName);
        if (mediaFiles == null) {
            log.error("文件信息刺进数据库失利 mergeChunk failed");
            MyException.cast(Code.SERVICE_BUSY);
            return false;
        }
        // 5.清理分块文件
        clearChunkFiles(chunkFilePath, chunkTotal);
        return true;
    }
    /**
     * 铲除分块文件
     *
     * @param chunkFilePath 分块文件途径
     * @param chunkTotal    分块文件总数
     */
    private void clearChunkFiles(String chunkFilePath, int chunkTotal) {
        Iterable<DeleteObject> objects = Stream.iterate(0, i -> ++i).limit(chunkTotal).map(i -> new DeleteObject(chunkFilePath + i)).collect(Collectors.toList());
        RemoveObjectsArgs removeObjectsArgs = RemoveObjectsArgs.builder().bucket(bucket_video).objects(objects).build();
        Iterable<Result<DeleteError>> results = minioClient.removeObjects(removeObjectsArgs);
        //要想真正删去
        results.forEach(f -> {
            try {
                DeleteError deleteError = f.get();
            } catch (Exception e) {
                e.printStackTrace();
            }
        });
    }
    /**
     * 得到分块文件的目录
     *
     * @param fileMd5 文件md5
     * @return
     */
    private String getChunkFilePath(String fileMd5) {
        return fileMd5.substring(0, 1) + "/" + fileMd5.substring(1, 2) + "/" + fileMd5 + "/" + "chunk" + "/";
    }
    /**
     * 将文件上传到minio
     *
     * @param localFilePath 文件本地途径
     * @param mimeType      媒体类型
     * @param bucket        桶
     * @param filePath      文件途径
     * @return
     */
    public boolean uploadFilesToMinIO(String localFilePath, String mimeType, String bucket, String filePath) {
        try {
            UploadObjectArgs uploadObjectArgs = UploadObjectArgs.builder()
                    .bucket(bucket)//桶
                    .filename(localFilePath) //指定本地文件途径
                    .object(filePath)// 文件名 放在子目录下
                    .contentType(mimeType)//设置媒体文件类型
                    .build();
            //上传文件
            minioClient.uploadObject(uploadObjectArgs);
            log.debug("上传文件到minio成功,bucket:{},objectName:{},错误信息:{}", bucket, filePath);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            log.error("上传文件犯错,bucket:{},objectName:{},错误信息:{}", bucket, filePath, e.getMessage());
        }
        return false;
    }
    /**
     * 增加文件到数据库
     *
     * @param fileMd5             文件md5值
     * @param uploadFileParamsDto 上传文件的信息
     * @param bucket              桶
     * @param filePath            文件存储途径
     */
    @Transactional
    public MediaFiles addFilesToDb(String fileMd5, UploadFileParamsDto uploadFileParamsDto, String bucket, String filePath) {
        //将文件信息保存到数据库
        LambdaQueryWrapper<MediaFiles> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(MediaFiles::getFileId, fileMd5);
        MediaFiles mediaFiles = mediaFilesMapper.selectOne(wrapper);
        if (mediaFiles == null) {
            mediaFiles = new MediaFiles();
            BeanUtils.copyProperties(uploadFileParamsDto, mediaFiles);
            // 文件途径
            mediaFiles.setFilePath(filePath);
            // 文件id
            mediaFiles.setFileId(fileMd5);
            // 文件地址
            mediaFiles.setUrl("http://192.168.31.32:9000" + "/" + bucket + "/" + filePath);
            //刺进数据库
            mediaFiles.setId(null);
            int insert = mediaFilesMapper.insert(mediaFiles);
            if (insert <= 0) {
                log.debug("向数据库保存文件失利,bucket:{},objectName:{}", bucket, filePath);
                return null;
            }
            return mediaFiles;
        }
        return mediaFiles;
    }
}

前端分片上传

React+TS完成分片上传 – ()