跳至主要內容

Minio

sixkey大约 7 分钟后端SpringBootMinio实战

Minio

1什么是minio?

MinIO 是一款高性能、分布式的对象存储系统. 它是一款软件产品, 可以100%的运行在标准硬件。即X86等低成本机器也能够很好的运行MinIO。

MinIO提供高性能、S3兼容的对象存储。Minio 是一个基于Go语言的对象存储服务。它实现了大部分亚马逊S3云存储服务接口,可以看做是是S3的开源版本,非常适合于存储大容量非结构化的数据,例如图片、视频、日志文件、备份数据和容器/虚拟机镜像等,而一个对象文件可以是任意大小,从几kb到最大5T不等。区别于分布式存储系统,minio的特色在于简单、轻量级,对开发者友好,认为存储应该是一个开发问题而不是一个运维问题。

MinIO是Kubernetes的原生产品,是唯一一个可在每个公共云、每个Kubernetes发行版、私有云和边缘上使用的对象存储套件。MinIO是软件定义的,在GNU AGPL v3下是100%开源的。

MinIO与传统的存储和其他的对象存储不同的是:它一开始就针对性能要求更高的私有云标准进行软件架构设计。因为MinIO一开始就只为对象存储而设计。所以他采用了更易用的方式进行设计,它能实现对象存储所需要的全部功能,在性能上也更加强劲,它不会为了更多的业务功能而妥协,失去MinIO的易用性、高效性。 这样的结果所带来的好处是:它能够更简单的实现局有弹性伸缩能力的原生对象存储服务。

MinIO 在最大数量的环境中支持最广泛的用例。自推出云原生以来,MinIO 的软件定义套件在公共云、私有云和 边缘无缝运行- 使其成为混合云和多云对象存储的领导者。凭借行业领先的性能 和可扩展性,MinIO 可以提供一系列用例,包括 AI/ML、分析、备份/恢复以及现代 Web 和移动应用程序。

MinIO在传统对象存储用例(例如辅助存储,灾难恢复和归档)方面表现出色。同时,它在机器学习、大数据、私有云、混合云等方面的存储技术上也独树一帜。当然,也不排除数据分析、高性能应用负载、原生云的支持。

在中国:阿里巴巴、腾讯、百度、中国联通、华为、中国移动等等9000多家企业也都在使用MinIO产品

  • 社区地址 - https://slack.min.io
  • 文档地址(文档推荐)- https://docs.min.io
  • 中文文档-http://docs.minio.org.cn/docs/
  • 博客地址- https://blog.min.io
  • 官网地址- https://min.io

2、集成

Windows

官网

MinIO | 高性能, Kubernetes 原生对象存储open in new window

启动

这里以我的为例:

将minio.exe放在了D盘

D:\minio\minio.exe

然后在D:\minio下新建data文件夹用来存放上传的文件

D:\minio\data

最后,在D:\minio路径上输入cmd进入命令行

输入:

**minio.exe server D:\minio\data **

minio默认启动端口号为9000

直接访问http://localhost:9000即可

箭头所指的就是登录密码和账户

Linux

下载安装

https://dl.min.io/server/minio/release/linux-amd64/minio

创建 minio 文件夹以及 data文件夹

mkdir /home/minio
mkdir /home/minio/data

将下载好的文件放入/home/minio中

先进入/home/minio中赋值权限

chmod +x minio

尝试启动

./minio server /home/minio/data

出现这个提示则添加映射路径

./minio server --console-address '0.0.0.0:9999'  /home/minio/data
 
nohup ./minio server --console-address '0.0.0.0:9999'  /home/minio/data &  #后台启动

说明:9999端口号是控制台端口号,整合文件上传时端口号不能写它。

警告说的是建议修改账号密码 默认账号密码为minioadmin端口为9000

export MINIO_ACCESS_KEY=XXXXXX
export MINIO_SECRET_KEY=XXXXXX

说明:9000端口号是控制台端口号,整合文件上传时端口号写它。

现在直接访问,若出现进不去页面大概是没开放端口,因为映射了端口9999所以需要开放9000和9999

开放端口命令

 
firewall-cmd --zone=public --add-port=9000/tcp --permanent
 
firewall-cmd --zone=public --add-port=9999/tcp --permanent
 
firewall-cmd --reload

访问路径

http://ip/9000

注意:密码不对也不会启动成功

java操作

导入依赖

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

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

    <!--minio依赖-->
    <dependency>
      <groupId>io.minio</groupId>
      <artifactId>minio</artifactId>
      <version>8.4.3</version>
    </dependency>

    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-test</artifactId>
    </dependency>
    <!---->
    <!-- knife4j -->
    <dependency>
      <groupId>com.github.xiaoymin</groupId>
      <artifactId>knife4j-spring-boot-starter</artifactId>
      <version>3.0.3</version>
    </dependency>

    <!-- swagger3 -->
    <dependency>
      <groupId>io.springfox</groupId>
      <artifactId>springfox-boot-starter</artifactId>
      <version>3.0.0</version>
    </dependency>
  </dependencies>

配置文件

server:
  port: 8081
spring:
  servlet:
    multipart:
      max-file-size: 100MB
      max-request-size: 100MB

minio:
  endpoint: http://localhost:9000
  accessKey: minioadmin
  secretKey: minioadmin
  bucketName: test

Minio客户端配置类

@Data
@Configuration
public class MinioClientConfig {

    @Value("${minio.endpoint}")
    private String endpoint;

    @Value("${minio.accessKey}")
    private String accessKey;

    @Value("${minio.secretKey}")
    private String secretKey;

    @Value("${minio.bucketName}")
    private String bucketName;

    //注入MinioClient客户端
    @Bean
    public MinioClient minioClient(){
        return MinioClient.builder()
                .endpoint(endpoint)
                .credentials(accessKey,secretKey)
                .build();
    }

}

Minio常用方法

@Slf4j
@Component
public class MinioUtil {

    @Autowired
    private MinioClient minioClient;

    @Value("${minio.bucketName}")
    private String bucketName;

    @Value("${minio.endpoint}")
    private String endpoint;

    /**
     * 判断bucket是否存在
     * @param name
     */
    public boolean existBucket(String name){
        //根据bucketName查看是否存在
        try {
            boolean exists = minioClient.bucketExists(BucketExistsArgs.builder()
                    .bucket(name)
                    .build());
            if(!exists){
                return false;
            }
        }catch (Exception e){
            e.printStackTrace();
        }
        return true;
    }

    /**
     * 创建存储bucketName
     * @param name
     * @return
     */
    public boolean makeBucket(String name){
        try {
            minioClient.makeBucket(MakeBucketArgs.builder()
                    .bucket(name)
                    .build());
        }catch (Exception e){
            e.printStackTrace();
            return false;
        }
        return true;
    }

    /**
     * 删除存储bucketName
     * @param name
     * @return
     */
    public boolean removeBucket(String name){
        try {
            minioClient.removeBucket(RemoveBucketArgs.builder()
                    .bucket(name)
                    .build());
        }catch (Exception e){
            e.printStackTrace();
            return false;
        }
        return true;
    }
    
    /**
     * description: 上传文件
     *
     * @param multipartFile
     * @return: java.lang.String
     */
    public List<String> upload(MultipartFile[] multipartFile) {
        List<String> names = new ArrayList<>(multipartFile.length);
        for (MultipartFile file : multipartFile) {
            //获取文件原始名:2.jpg
            String fileName = file.getOriginalFilename();
            //必须写出\\.这是转义,将文件名从.开始分割成 2 和 jpg
            String[] split = fileName.split("\\.");
            if (split.length > 1) {
                //防止文件重名:添加系统时间:文件名为:2_37598375837.jpg
                //方式1:
                //fileName = split[0] + "_" + System.currentTimeMillis() + "." + split[1];
                //方式2:
                // 完善1、 --> 在文件名中添加唯一值
                String uuid = UUID.randomUUID().toString().replace("-", "");
                fileName = uuid + split[0] + "." + split[1];
                // 完善2、 --> 把文件按照日期分类
                String datePath = new DateTime().toString("yyyy/MM/dd");
                // 拼接时间 yyyy/MM/dd/filename
                fileName = datePath + "/" + fileName;
            } else {
                fileName = fileName + System.currentTimeMillis();
            }
            InputStream in = null;
            try {
                //开始上传
                in = file.getInputStream();
                minioClient.putObject(PutObjectArgs.builder()
                        .bucket(bucketName)
                        .object(fileName)
                        .stream(in, in.available(), -1)
                        .contentType(file.getContentType())
                        .build()
                );
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                //关闭流
                if (in != null) {
                    try {
                        in.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
            fileName = endpoint + "/" + bucketName + "/" + fileName;
            names.add(fileName);
        }
        //返回图片地址url
        return names;
    }
    

    /**
     * description: 下载文件
     *
     * @param fileName
     * @return: org.springframework.http.ResponseEntity<byte [ ]>
     */
    public ResponseEntity<byte[]> download(String fileName) {
        ResponseEntity<byte[]> responseEntity = null;
        InputStream in = null;
        ByteArrayOutputStream out = null;
        try {
            //通过文件名获取
            in = minioClient.getObject(GetObjectArgs.builder().bucket(bucketName).object(fileName).build());
            out = new ByteArrayOutputStream();
            IOUtils.copy(in, out);
            //封装返回值
            byte[] bytes = out.toByteArray();
            HttpHeaders headers = new HttpHeaders();
            try {
                headers.add("Content-Disposition", "attachment;filename=" + URLEncoder.encode(fileName, "UTF-8"));
            } catch (UnsupportedEncodingException e) {
                e.printStackTrace();
            }
            headers.setContentLength(bytes.length);
            headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);
            headers.setAccessControlExposeHeaders(Arrays.asList("*"));
            responseEntity = new ResponseEntity<byte[]>(bytes, headers, HttpStatus.OK);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                if (in != null) {
                    try {
                        in.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
                if (out != null) {
                    out.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return responseEntity;
    }

    /**
     * 查看文件对象
     * @param bucketName 存储bucket名称
     * @return 存储bucket内文件对象信息
     */
    public List<MinioBean> listObjects(String bucketName) {
        Iterable<Result<Item>> results = minioClient.listObjects(
                ListObjectsArgs.builder().bucket(bucketName).build());
        List<MinioBean> objectItems = new ArrayList<>();
        try {
            for (Result<Item> result : results) {
                Item item = result.get();
                MinioBean objectItem = new MinioBean();
                objectItem.setName(item.objectName());
                objectItem.setSize(item.size());
                objectItems.add(objectItem);
            }
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
        return objectItems;
    }

    /**
     * 删除指定文件
     * @param name
     * @param file
     */
    public boolean deleteFile(String name,String file){
        try {
            minioClient.removeObject(RemoveObjectArgs.builder().bucket(name).object(file).build());
        }catch (Exception e){
            return false;
        }
        return true;
    }
    
    //批量删除文件
    //批量删除Minio里面的图片
        for (Photo photo : photoList) {
            String url = photo.getUrl();
            String fileName = url.substring(url.lastIndexOf("/") + 1);
            minioClient.removeObject(RemoveObjectArgs.builder().bucket(MinioClientConfig.BUCKET_NAME).object(fileName).build());
        }
}

获取文件信息的一个实体类

@Data
@Component
@AllArgsConstructor
@NoArgsConstructor
public class MinioBean {
    private String name;
    private Long size;
}

控制层

@Api(tags = "Minio测试")
@RestController
public class MinioController {
    @Autowired
    private MinioUtil minioUtil;

    //多文件上传,因为是一个数组接收,所以可以一次性上传多个文件
    @PostMapping("/upload")
    public Object upload(MultipartFile[] file) {
        List<String> upload = minioUtil.upload(file);
        return upload;
    }

    //判断bucket是否存在
    @GetMapping("/exist")
    public boolean exists(@RequestParam("bucketName") String bucketName){
        return minioUtil.existBucket(bucketName);
    }

    //创建存储bucketName
    @PostMapping("/create")
    public boolean makeBucket(@RequestParam("bucketName") String bucketName){
        return minioUtil.makeBucket(bucketName);
    }

    //删除存储bucketName
    @DeleteMapping("/delete")
    public boolean deleteBucket(@RequestParam("bucketName") String bucketName){
        return minioUtil.removeBucket(bucketName);
    }

    @PostMapping("/download")
    public Object download(@RequestParam("fileName") String fileName){
        return minioUtil.download(fileName);
    }

    //查看文件对象
    @ApiOperation(value = "查看文件对象")
    @GetMapping("/list")
    public List<MinioBean> list(@RequestParam("bucketName") String bucketName){
        return minioUtil.listObjects(bucketName);
    }


    @ApiOperation(value = "批量删除文件对象")
    //批量删除文件对象
    @DeleteMapping("/batchDelete/{bucketName}")
    public boolean batchDeleteBucket(@PathVariable("bucketName") String bucketName, @RequestParam List<String> fileNames) throws ServerException, InsufficientDataException, ErrorResponseException, IOException, NoSuchAlgorithmException, InvalidKeyException, InvalidResponseException, XmlParserException, InternalException {
        return minioUtil.batchDeleteBucket(bucketName,fileNames);
    }
}

3、测试小插曲

List传参数问题

@ApiOperation(value = "批量删除文件对象")
    //批量删除文件对象
    @DeleteMapping("/batchDelete/{bucketName}")
    public boolean batchDeleteBucket(@PathVariable("bucketName") String bucketName, @RequestParam List<String> fileNames) throws ServerException, InsufficientDataException, ErrorResponseException, IOException, NoSuchAlgorithmException, InvalidKeyException, InvalidResponseException, XmlParserException, InternalException {
        return minioUtil.batchDeleteBucket(bucketName,fileNames);
    }

测试时逗号连接即可

4、文件返回地址展示