spring boot + minio 分布式文件上传

介绍

1、分布式文件系统

简单理解为:一个计算机无法存储海量的文件,通过网络将若干计算机组织起来共同去存储海量的文件,去接收海量用户的请求,这些组织起来的计算机通过网络进行通信。

好处:

  • 一台计算机的文件系统处理能力扩充到多台计算机同时处理。
  • 一台计算机挂了还有另外副本计算机提供数据。
  • 每台计算机可以放在不同的地域,这样用户就可以就近访问,提高访问速度。

在这里插入图片描述

2、市面上有哪些分布式文件系统的产品呢?

2.1、网络文件系统(NFS)

特点:

  • 在客户端上映射NFS服务器的驱动器。
  • 客户端通过网络访问NFS服务器的硬盘完全透明。

2.2、GFS

特点:

  • GFS采用主从结构,一个GFS集群由一个master和大量的chunkserver组成。
  • master存储了数据文件的元数据,一个文件被分成了若干块存储在多个chunkserver中。
  • 用户从master中获取数据元信息,向chunkserver存储数据。

2.3、HDFS

HDFS,是Hadoop Distributed File System的简称,是Hadoop抽象文件系统的一种实现。HDFS是一个高度容错性的系统,适合部署在廉价的机器上。HDFS能提供高吞吐量的数据访问,非常适合大规模数据集上的应用。 HDFS的文件分布在集群机器上,同时提供副本进行容错及可靠性保证。例如客户端写入读取文件的直接操作都是分布在集群各个机器上的,没有单点性能压力。

特点:

  • HDFS采用主从结构,一个HDFS集群由一个名称结点和若干数据结点组成。
  • 名称结点存储数据的元信息,一个完整的数据文件分成若干块存储在数据结点。
  • 客户端从名称结点获取数据的元信息及数据分块的信息,得到信息客户端即可从数据块来存取数据。

2.4、云计算厂家

  • 阿里云对象存储服务(Object Storage Service,简称 OSS),是阿里云提供的海量、安全、低成本、高可靠的云存储服务。其数据设计持久性不低于 99.9999999999%(12 个 9),服务设计可用性(或业务连续性)不低于 99.995%。
    官方网站:https://www.aliyun.com/product/oss
  • 百度对象存储BOS提供稳定、安全、高效、高可扩展的云存储服务。您可以将任意数量和形式的非结构化数据存入BOS,并对数据进行管理和处理。BOS支持标准、低频、冷和归档存储等多种存储类型,满足多场景的存储需求。
    官方网站:https://cloud.baidu.com/product/bos.html

3、MinIO

3.1、介绍

官网:https://min.io
中文:https://www.minio.org.cn/,http://docs.minio.org.cn/docs/

本项目采用MinIO构建分布式文件系统,MinIO 是一个非常轻量的服务,可以很简单的和其他应用的结合使用,它兼容亚马逊 S3 云存储服务接口,非常适合于存储大容量非结构化的数据,例如图片、视频、日志文件、备份数据和容器/虚拟机镜像等。

它一大特点就是轻量,使用简单,功能强大,支持各种平台,单个文件最大5TB,兼容 Amazon S3接口,提供了 Java、Python、GO等多版本SDK支持。

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

去中心化有什么好处?

  • 在大数据领域,通常的设计理念都是无中心和分布式。Minio分布式模式可以帮助你搭建一个高可用的对象存储服务,你可以使用这些存储设备,而不用考虑其真实物理位置。
  • 它将分布在不同服务器上的多块硬盘组成一个对象存储服务。由于硬盘分布在不同的节点上,分布式Minio避免了单点故障。如下图:
    在这里插入图片描述

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

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

3.2、Windows安装测试

3.2.1、下载安装

在官网下载minio.exe文件即可
地址:https://www.minio.org.cn/download.shtml#/windows
在这里插入图片描述

3.2.2、启动

CMD进入有minio.exe的目录,运行下边的命令:

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

在这里插入图片描述

  • 老版本使用的MINIO_ACCESS_KEY 和 MINIO_SECRET_KEY不推荐使用,推荐使用MINIO_ROOT_USER 和MINIO_ROOT_PASSWORD设置账号和密码。
  • pool即minio节点组成的池子,当前有一个pool和4个硬盘组成的set集合
  • 因为集合是4个硬盘,大于2的硬盘损坏数据将无法恢复。
  • 账号和密码默认为minioadmin、minioadmin,可以在环境变量中设置通过’MINIO_ROOT_USER’ and ‘MINIO_ROOT_PASSWORD’ 进行设置。
3.2.3、访问,登录

http://localhost:9000
默认账号密码:minioadmin
在这里插入图片描述

3.3.4、创建Bucket桶
  • 系统页面创建
    在这里插入图片描述
    在这里插入图片描述
  • 存储目录查看
    在这里插入图片描述在这里插入图片描述在这里插入图片描述
3.2.5、上传文件
  • 系统页面上传
    在这里插入图片描述

在这里插入图片描述

  • 目录查看
    在这里插入图片描述

SDK操作(java代码)

1、引入依赖

        <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>
        <!-- 测试的依赖-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
        </dependency>

2、上传文件

连接

  • endpoint:minio服务的地址
  • credentials:用户密码

上传

  • bucket:桶的名称
  • object:上传后的对象名称
  • filename:要上传的文件的路径
public class MinIOTest {
    
    

//    连接minio
    static MinioClient minioClient =
            MinioClient.builder()
                    .endpoint("http://localhost:9000")
                    .credentials("minioadmin", "minioadmin")
                    .build();


    @Test
    public void upload() {
    
    

        try {
    
    
            UploadObjectArgs uploadObjectArgs = UploadObjectArgs.builder()
                    .bucket("myfile")
                    .object("刘亦菲.jpeg")//同一个桶内对象名不能重复
                    .filename("C:\\Users\\86152\\Pictures\\Camera Roll\\lyf01.jpeg")
                    .build();
            //上传
            minioClient.uploadObject(uploadObjectArgs);
            System.out.println("上传成功了");
        } catch (Exception e) {
    
    
            System.out.println("上传失败");
        }
    }
}

3、删除文件

    //删除文件
    @Test
    public void delete() {
    
    

        try {
    
    
            RemoveObjectArgs removeObjectArgs = RemoveObjectArgs
                    .builder()
                    .bucket("myfile")
                    .object("刘亦菲.jpeg")
                    .build();
            minioClient.removeObject(removeObjectArgs);
        } catch (Exception e) {
    
    
        }
    }

没有了
在这里插入图片描述

4、查询文件(下载文件)

//查询文件
    @Test
    public void getFile() {
    
    
        GetObjectArgs getObjectArgs = GetObjectArgs.builder().bucket("myfile").object("高圆圆.jpeg").build();
        try(
                FilterInputStream inputStream = minioClient.getObject(getObjectArgs);
                FileOutputStream outputStream = new FileOutputStream(new File("D:\\my\\minio_data\\gyy.jpeg"));
                ) {
    
    

            if(inputStream!=null){
    
    
                IOUtils.copy(inputStream,outputStream);
            }
        } catch (Exception e) {
    
    
        }

    }

在这里插入图片描述

文件上传(实战)

1、引入依赖

<dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

        <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>
        <!-- 测试的依赖-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>

    <!--mybatis-plus-->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.4.1</version>
        </dependency>

        <!-- mybatis plus 代码生成器 -->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-generator</artifactId>
            <version>3.4.1</version>
        </dependency>

        <dependency>
            <groupId>io.swagger</groupId>
            <artifactId>swagger-annotations</artifactId>
            <version>1.5.9</version>
        </dependency>

        <!-- druid 连接池管理 -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>1.2.8</version>
        </dependency>

        <!-- mySQL数据库驱动包管理 -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.6</version>
        </dependency>

        <!-- fastjson ,json解析工具 -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.83</version>
        </dependency>

        <!-- 工具类管理 -->
        <dependency>
            <groupId>commons-lang</groupId>
            <artifactId>commons-lang</artifactId>
            <version>2.6</version>
        </dependency>

        <!--google推荐的一套工具类库-->
        <dependency>
            <groupId>com.google.guava</groupId>
            <artifactId>guava</artifactId>
            <version>25.0-jre</version>
        </dependency>

        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
            <version>3.10</version>
        </dependency>

        <!-- Servlet 容器管理 -->
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>javax.servlet-api</artifactId>
            <version>4.0.1</version>
            <scope>provided</scope>
        </dependency>

        <!-- Spring Boot 集成 log4j2 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-log4j2</artifactId>
        </dependency>

        <!-- 排除 Spring Boot 依赖的日志包冲突 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
            <exclusions>
                <exclusion>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-starter-logging</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
    </dependencies>

2、环境配置

server.port=9001
spring.application.name=file


spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url: jdbc:mysql://localhost:3306/hm?serverTimezone=UTC&userUnicode=true&useSSL=false&
spring.datasource.username: root
spring.datasource.password: zzybzb


# 日志文件配置路径
logging.config=classpath:log4j2-dev.xml

minio.endpoint=http://localhost:9000
minio.accessKey=minioadmin
minio.secretKey=minioadmin
minio.bucket.files=myfile

spring.servlet.multipart.max-file-size=50MB
spring.servlet.multipart.max-request-size=50MB

3、关键代码

3.1、minio配置类

package com.file.config;

import io.minio.MinioClient;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
    minio配置类
 */
@Configuration
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;
    }
}

3.2、controller层代码

    @RequestMapping(value = "/upload/coursefile", consumes = {
    
    MediaType.MULTIPART_FORM_DATA_VALUE})
    public UploadFileResultDto upload(@RequestPart("filedata") MultipartFile filedata,
                                      @RequestParam(value = "folder",required=false) String folder,
                                      @RequestParam(value= "objectName",required=false) String objectName) {
    
    

        Long companyId = 1232141425L;
        UploadFileParamsDto uploadFileParamsDto = new UploadFileParamsDto();
        String contentType = filedata.getContentType();
        uploadFileParamsDto.setContentType(contentType);
        uploadFileParamsDto.setFileSize(filedata.getSize());//文件大小
        if (contentType.indexOf("image") >= 0) {
    
    
            //是个图片
            uploadFileParamsDto.setFileType("001001");
        } else {
    
    
            uploadFileParamsDto.setFileType("001003");
        }
        uploadFileParamsDto.setFilename(filedata.getOriginalFilename());//文件名称
        UploadFileResultDto uploadFileResultDto = null;
        try {
    
    
            uploadFileResultDto = mediaFileService.uploadFile(companyId, uploadFileParamsDto, filedata.getBytes(), folder, objectName);
        } catch (Exception e) {
    
    
            XueChengPlusException.cast("上传文件过程中出错");
        }

        return uploadFileResultDto;

    }

3.3、service层代码

  • service
public interface MediaFileService {
    
    
	 /**
	  * @description 上传文件的通用接口
	  * @param companyId  机构id
	  * @param uploadFileParamsDto  文件信息
	  * @param bytes  文件字节数组
	  * @param folder 桶下边的子目录
	  * @param objectName 对象名称
	 */
	 public UploadFileResultDto uploadFile(Long companyId, UploadFileParamsDto uploadFileParamsDto, byte[] bytes, String folder, String objectName);

}
  • impl代码
@Slf4j
@Service
public class MediaFileServiceImpl implements MediaFileService {
    
    

    @Autowired
    MediaFilesMapper mediaFilesMapper;

    @Autowired
    MinioClient minioClient;

    @Value("${minio.bucket.files}")
    private String bucket_files;

    @Override
    public UploadFileResultDto uploadFile(Long companyId, UploadFileParamsDto uploadFileParamsDto, byte[] bytes, String folder, String objectName) {
    
    


        //得到文件的md5值
        String fileMd5 = DigestUtils.md5DigestAsHex(bytes);

        if(StringUtils.isEmpty(folder)){
    
    
            //自动生成目录的路径 按年月日生成,
            folder = getFileFolder(new Date(), true, true, true);
        }else if(folder.indexOf("/")<0){
    
    
            folder = folder+"/";
        }
        //文件名称
        String filename = uploadFileParamsDto.getFilename();

        if(StringUtils.isEmpty(objectName)){
    
    
            //如果objectName为空,使用文件的md5值为objectName
            objectName = fileMd5 + filename.substring(filename.lastIndexOf("."));
        }

        objectName = folder + objectName;

        try {
    
    
            ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes);
            String contentType = uploadFileParamsDto.getContentType();

            PutObjectArgs putObjectArgs = PutObjectArgs.builder()
                    .bucket(bucket_files)
                    .object(objectName)
                    //InputStream stream, long objectSize 对象大小, long partSize 分片大小(-1表示5M,最大不要超过5T,最多10000)
                    .stream(byteArrayInputStream, byteArrayInputStream.available(), -1)
                    .contentType(contentType)
                    .build();
            //上传到minio
            minioClient.putObject(putObjectArgs);

            //保存到数据库
            MediaFiles mediaFiles = mediaFilesMapper.selectById(fileMd5);
            if(mediaFiles == null){
    
    
                mediaFiles = new MediaFiles();

                //封装数据
                BeanUtils.copyProperties(uploadFileParamsDto,mediaFiles);
                mediaFiles.setId(fileMd5);
                mediaFiles.setFileId(fileMd5);
                mediaFiles.setCompanyId(companyId);
                mediaFiles.setFilename(filename);
                mediaFiles.setBucket(bucket_files);
                mediaFiles.setFilePath(objectName);
                mediaFiles.setUrl("/"+bucket_files+"/"+objectName);
                mediaFiles.setCreateDate(new Date());
                mediaFiles.setStatus("1");
                mediaFiles.setAuditStatus("002003");

                //插入文件表
                mediaFilesMapper.insert(mediaFiles);

            }

            //准备返回数据
            UploadFileResultDto uploadFileResultDto = new UploadFileResultDto();
            BeanUtils.copyProperties(mediaFiles,uploadFileResultDto);
            return uploadFileResultDto;


        } catch (Exception e) {
    
    
            log.debug("上传文件失败:{}",e.getMessage());
        }

        return null;
    }

    //根据日期拼接目录
    private String getFileFolder(Date date, boolean year, boolean month, boolean day){
    
    
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
        //获取当前日期字符串
        String dateString = sdf.format(new Date());
        //取出年、月、日
        String[] dateStringArray = dateString.split("-");
        StringBuffer folderString = new StringBuffer();
        if(year){
    
    
            folderString.append(dateStringArray[0]);
            folderString.append("/");
        }
        if(month){
    
    
            folderString.append(dateStringArray[1]);
            folderString.append("/");
        }
        if(day){
    
    
            folderString.append(dateStringArray[2]);
            folderString.append("/");
        }
        return folderString.toString();
    }
}

3.4、mapper层代码

@Mapper
public interface MediaFilesMapper extends BaseMapper<MediaFiles> {
    
    

}

4、启动项目,测试

  • 使用httpclient测试
### 上传文件
POST http://localhost:9001/upload/coursefile
Content-Type: multipart/form-data; boundary=WebAppBoundary

--WebAppBoundary
Content-Disposition: form-data; name="filedata"; filename="widget-3.jpg"
Content-Type: application/octet-stream

< C:\Users\86152\Pictures\Camera Roll\lyf01.jpeg

在这里插入图片描述

  • 执行结果
    在这里插入图片描述
  • 查看数据库
    在这里插入图片描述
  • 查看miniO存储目录
    在这里插入图片描述

上传成功。

结束!!!
hy:11


										谎言最大的伤害是让人们不再相信真相。---苏格拉底

猜你喜欢

转载自blog.csdn.net/weixin_49107940/article/details/130997377