springboot整合minio(实现文件的上传和下载超详细入门)
作者:mmseoamin日期:2023-12-21

一、Minio介绍:

目前可用于文件存储的网络服务选择也有不少,比如阿里云OSS、七牛云、腾讯云等等,可是收费都有点小贵。为了省钱,很多公司使用MinIO做为文件服务器。

官网:https://www.minio.org.cn/

 MinIO是一个开源的分布式对象存储服务器,支持S3协议并且可以在多节点上实现数据的高可用和容错。它采用Go语言开发,拥有轻量级、高性能、易部署等特点,并且可以自由选择底层存储介质。它基于Apache License 开源协议,兼容Amazon S3云存储接口。适合存储非结构化数据,如图片,音频,视频,日志等。

二、Minio的下载:

有Windows和Linux两种方式,不过我们一般把服务器下载到Linux中。使用docker来部署,能够很方便的管理(默认你的Linux中已经下载好docker了)。

//拉取镜像

docker pull quay.io/minio/minio

// 创建数据存储目录

mkdir -p ~/minio/data

// 创建minio

docker run \

   -p 9001:9000 \

   -p 9090:9090 \

   --name minio \

   -v ~/minio/data:/data \

   -e "MINIO_ROOT_USER=admin" \

   -e "MINIO_ROOT_PASSWORD=admin123456" \

   -d \

   quay.io/minio/minio server /data --console-address ":9090"

在这里解释一下这个命令:

docker pull 拉取Minio的镜像,我没有指定版本,所以默认下载最新版本。

mkdir -p ~/minio/data:创建一个文件夹来存储我们上传到Minio中的文件。

实际上,我们上传到Minio中的文件都存储到了这个文件夹中。也就是说我们上传到Minio中的文件其实都保存在你的Linux服务器中。

docker run :创建容器并运行。解释一下重要的几个参数。

9001:Minio的服务端端口

9090:Minio的客户端端口

-v ~/minio/data:/data :数据卷挂载我们之前创建的文件夹,使之成为Minio的存储容器

-e "MINIO_ROOT_USER=admin" :用户名为admin

-e "MINIO_ROOT_PASSWORD=admin123456" :密码为admin123456

将容器运行起来:

springboot整合minio(实现文件的上传和下载超详细入门),第1张

现在登录我们的客户端,并输入用户名和密码:

http://Ip地址:9090

springboot整合minio(实现文件的上传和下载超详细入门),第2张

我们还要了解一下“桶”在Minio中的概念:

"桶"(Bucket)是用来组织和管理存储的对象(文件或文件夹)的基本单位。你可以把它想象成一个容器,用来存放你的对象。

以下是关于桶的一些重要概念:

  1. 命名:每个桶都有一个全局唯一的名称。这意味着在同一个MinIO服务中,不能有两个名称相同的桶。

  2. 隔离:桶之间是完全隔离的。一个桶中的对象不能直接访问另一个桶中的对象。

  3. 访问控制:每个桶都可以有自己的访问控制策略。例如,你可以设置一个桶为公开,任何人都可以读取它的内容;也可以设置一个桶为私有,只有特定的用户可以访问它的内容。

  4. 无限制的对象存储:每个桶内可以存储无限数量的对象,只要你的存储空间足够。

  5. 元数据:每个桶都可以有一些元数据,如创建时间、修改时间等。

可以选择在客户端创建桶,也可以使用Java代码来创建,我这里就使用Java代码创建桶了。

三、使用spring boot整合Minio,完成文件的上传、下载和删除。

版本:spring boot3、jdk17、Minio的版本为:RELEASE.2023-11-01T18-37-25Z

本次使用knife4j进行测试;

创建一个空的spring boot项目,并引入如下依赖:


    
        org.springframework.boot
        spring-boot-starter-web
    
    
        com.github.xiaoymin
        knife4j-openapi3-jakarta-spring-boot-starter
        4.3.0
    
    
        io.minio
        minio
        8.5.2
    

    org.projectlombok
    lombok

在yml配置文件中引入如下配置:

minio:
  url: http://192.168.231.110:9001
  username: admin
  password: admin123456
  bucketName: test
spring:
  servlet:
    multipart:
      max-file-size:  10MB   # 单个文件上传的最大上限
      max-request-size:  100MB  # 整个请求体的最大上限

minio:我们自定义的属性

url:你的服务端地址

username:用户名

password:密码

bucketName:test(桶的名称,我们现在没有创建,一会用Java代码实现)

max-file-size:一定要配置这个属性,不然springboot默认的单个文件上传的最大上限为1MB,超过这个上限,就会报错。

1、创建一个实体类来继承yml文件中minio的连接信息;

@ConfigurationProperties(prefix = "minio")
@Component
@Data
public class MinioPojo {
private String url;
private String username;
private String password;
private String bucketName;
}

2、自定义bean,将MinioClient初始化;

@Configuration
public class MinioConfig {
    @Autowired
    private MinioPojo minioPojo;
@Bean
    public MinioClient minioClient(){
    return MinioClient.builder()
            .endpoint(minioPojo.getUrl())  //传入url地址
                //传入用户名和密码
            .credentials(minioPojo.getUsername(), minioPojo.getPassword())
            .build();  //完成MinioClient的初始化
    }
}

3、新创建一个MinioService类,用来进行文件的操作,并注册为bean。将来在这里进行具体代码的编写。

@Component
public class MinioService {
    @Autowired
    private MinioClient minioClient;
    @Autowired
    private MinioPojo minioPojo;

}

4、新建一个Controller,用来进行文件的上传。

@RestController
@RequestMapping("/tests")
public class TestController {
    @Autowired
    private MinioService minioService;
    @Autowired
    private MinioPojo minioPojo;
    //文件上传
    @Operation(summary = "上传图片")
    @PostMapping("/uploadImage")
    public String aa(MultipartFile file){
        String url = minioService.uploadImage(minioPojo.getBucketName(), file);
        return url;
    }

}

我们在MinioService中定义了uploadImage方法,传入了两个参数。一个是桶的名称,一个是文件file。(注意参数名称一定要叫file,因为现在前端默认传文件时都会叫这个名字,如果你随意的更改名字,后端会接收不到参数。)

返回文件在Minio中的地址url。(注意,只要你的容器Minio还在运行,就可以直接访问到。)

5、在MinioService中进行方法的实现。

  public String uploadImage(String bucketName, MultipartFile file) {
        try {
//判断桶是否存在
            boolean bucketExists = minioClient.bucketExists(BucketExistsArgs.builder().bucket(bucketName).build());
            if (!bucketExists){
//                如果不存在,就创建桶
                minioClient.makeBucket(MakeBucketArgs.builder().bucket(bucketName).build());
            }
//            本地时间,具体到年、月、日
            String yyyymmdd = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd"));
//            String uuid= UUID.randomUUID().toString();
            String filename = yyyymmdd+"/"+file.getOriginalFilename();
//          加一个/表示创建一个文件夹
            minioClient.putObject(PutObjectArgs.builder().
                            bucket(bucketName).
                            object(filename).
                            stream(file.getInputStream(), file.getSize(), -1).
//                    文件上传的类型,如果不指定,那么每次访问时都要先下载文件
                            contentType(file.getContentType()).
                            build());
String url= minioPojo.getUrl()+"/"+minioPojo.getBucketName()+"/"+filename;
return url;
        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException("文件上传失败");
        }
    }

现在,我们可以在knife4j中去测试发送信息了。

springboot整合minio(实现文件的上传和下载超详细入门),第3张

如图所示,我们发送文件成功了。返回了一个url地址,我们输入这个地址就可以直接访问到这张图片了

(注意,这里有坑。我们虽然能够上传文件,并返回了文件在Minio中的地址,但是我们现在并不能直接去访问,因为现在这个桶test默认的访问权限不够,我们要在Minio的客户端将这个桶的访问权限设为public。),创建存储桶时无法直接设置为公开访问,你需要在创建存储桶后再设置存储桶的策略来实现公开访问。这是因为MinIO遵循最小权限原则,以确保数据安全。

在进行文件的上传时,如果文件名称相同会进行覆盖。这时一般的方法为在每一次文件上传时,对文件生成一个UUID,以保证每一次上传的文件名称都不一样,以免覆盖。

springboot整合minio(实现文件的上传和下载超详细入门),第4张

也可以在Java代码中实现桶访问权限的修改,但是很麻烦。

// 设置存储桶的策略

String policyJson = "{\"Statement\"[{\"Action[\"s3:GetObject\"],\"Effect\":\"Allow\",\"Principal\":{\"AWS\":[\"*\"]},\"Resource\":[\"arn:aws:s3:::" + bucketName + "/*\"],\"Sid\":\"\"}],\"Version\":\"2012-10-17\"}"; minioClient.setBucketPolicy(SetBucketPolicyArgs.builder().bucket(bucketName).config(policyJson).build());

现在,我们就可以直接输入返回的url地址,来查看图片并下载。

springboot整合minio(实现文件的上传和下载超详细入门),第5张

我这次上传的文件类型是图片,其他的文件类型也可以上传。

6、实现文件的下载:

public String getObject(String url){
    try{
       InputStream inputStream = minioClient.getObject(GetObjectArgs.builder()
               .bucket(minioPojo.getBucketName())
                .object(url)
                .build());
       // 将输入流的内容复制到一个文件中
        Files.copy(inputStream, Path.of("D://test.png"),
                StandardCopyOption.REPLACE_EXISTING);
        return "文件上传成功";
    }catch (Exception e){
        e.printStackTrace();
    }
return "文件上传失败";
}

这里传入的url是在Minion,相对于桶的路径

 假如现在在Minio中有一个url地址:http://192.168.231.110:9001/test/2023-11-05/32cc63cb-eb0c-4398-8bb8-3ac37c3a130410.png

url:http://192.168.231.110:9001

bucketName:test

要传入的url参数为:

2023-11-05/32cc63cb-eb0c-4398-8bb8-3ac37c3a130410.png

7、实现文件的删除:

public void deleteFile(String bucketName,String url) {
    try {
       minioClient.removeObject(RemoveObjectArgs.builder()
               .bucket(bucketName)
               .object(url)
               .build());
    } catch (Exception e) {
        e.printStackTrace();
        throw new RuntimeException(url+"文件删除失败");
    }
}

文件的删除,要传入两个参数。一个是文件所在桶的名称,另一个是文件在Minio中,相对于桶的路径:现在在Minio中有一个url地址:http://192.168.231.110:9001/test/2023-11-05/32cc63cb-eb0c-4398-8bb8-3ac37c3a130410.png

url:http://192.168.231.110:9001

bucketName:test

要传入的url参数为:

2023-11-05/32cc63cb-eb0c-4398-8bb8-3ac37c3a130410.png

运行这个方法是在minio中将图片该删除掉了。

总结:

我们使用minio的常用操作就是,上传、下载和删除。

这些文件都存储在我们的Linux服务器中,并不能凭空存在,所以上传时要注意把握力度。如果上传的文件太多太大的话,Linux会先撑不住的。