docker-compose搭建minio对象存储服务器

news/2024/7/8 12:24:43 标签: docker, 服务器, minio

dockercomposeminio_1">docker-compose搭建minio对象存储服务器

最近想使用oss对象存储进行用户图片上传的管理,了解了一下例如aliyun或者腾讯云的oss对象存储服务,但是呢涉及到对象存储以及经费有限的缘故,决定自己手动搭建一个oss对象存储服务器

首先大致了解一下对象存储:

对象存储OSS(Object Storage Service)是一种云存储服务,它提供了海量、安全、低成本、高可靠的存储解决方案

然后在经过大致了解后,选择了MiNiO,进行oss对象服务器的搭建工作,MinIO是一个开源的对象存储服务器,它兼容Amazon S3 API,并提供高性能、高可用性的存储解决方案。在本文中,我们将介绍如何使用Docker Compose快速部署MinIO。

dockercomposeminio_11">一、docker-compose中的minio对象服务部署

准备工作:

1、服务器必须安装docker

2、服务器必须安装docker对应版本的docker-compose

1.1获取镜像:

首先,如果你的docker还能连上网,能够通过docker pull相关的镜像(咳咳,最近docker不对劲,拉取不到镜像),如果可以拉取镜像,可以执行下述命令:

docker pull minio/minio:latest

如果不可以,建议在往上下载一个minio的tar包,上传至服务器后,可以执行以下命令:

docker load -i minio.tar  ## minio 是你自己tar包的名字。

通过上述操作后,可以使用 docker images 进行查看获取到的镜像
请添加图片描述

dockercomposeyml_38">1.2 docker-compose.yml文件制作

vim docker-compose.yml

先贴一个代码叭,一会儿挨个儿解释:

version: '3'
services:
  minio:
    image: minio/minio
    container_name: minio
    ports:
      - 9010:9000
      - 9011:9011
    environment:
      TZ: Asia/Shanghai
      MINIO_ACCESS_KEY: minio
      MINIO_SECRET_KEY: minio123
    volumes:
      - ./data:/data
    command: server /data --console-address ":9011"

大概配置如上所示,有几点注意

注:

1、minio容器默认使用两个端口,9000和9001 9000端口主要适用于数据传输,9001端口主要是用于管理界面,上述文件中我为了好记且避免端口冲突,将9000端口映射到了服务器的9010端口,将9001端口改成了9011并映射到了服务器的9011端口

2、数据卷映射: 默认将数据卷映射到了docker-compose.yml同文件目录下的data文件夹

3、command: server --console-address ‘:9011’ /data 这行一定要加,否则端口号是随机的,你压根映射不出去

4、新版本中用户名和密码改用成了 “MINIO_ROOT_USER” 和 “MINIO_ROOT_PASSWORD” 旧版本是 “MINIO_ACCESS_KEY” 和 “MINIO_SECRET_KEY” 可以自己按照版本进行设置。

5、4中分别对应的是管理界面的用户名和密码

在编辑docker-compose.yml并保存后,通过下述命令创建并启动minio容器

#如果你的docker-compose.yml文件中有好几个容器,你并不想启动其他容器,只想启动minio
docker-compose up -d minio
#如果你的docker-compose.yml文件中只有目前的minio
docker-compose up -d 

启动成功后会看到服务器显示

请添加图片描述

此时可以在浏览器输入 上面docker-compose.yml文件中的【你自己的IP+9011】 访问minio的控制面板,记得开启防火墙9010,9011端口哟~

请添加图片描述

输入用户名和密码,登录minio控制面板

请添加图片描述

至此呢 单机版本 通过docker-compose 部署minio对象存储结束

minio__104">二、spring-boot 集成 minio 对象存储

1、自己创建spring-boot 工程,在这里不多赘述

2、引入pom依赖

在自己的boot项目中引入minio依赖

<!---minio cos对象存储-->
        <dependency>
            <groupId>io.minio</groupId>
            <artifactId>minio</artifactId>
            <version>8.4.0</version>
        </dependency>

3、集成代码

在集成代码之前呢,首先了解一下minio的几个知识点

  • Object:存储到Minio的基本对象,如文件、字节流、Anything…
  • Bucket:用来存储Object的逻辑空间。每个Bucket之间的数据量是互相隔离的。对于客户端而言,就相当于一个存放文件的顶层文件夹。
  • Drive:即存储数据的磁盘,在Minio启动时,以参数的方式传入。Minio中所有的对象数据都会存储在Drive里。
  • Set:即一组Drive的集合,分布式部署根据集群规模自动划分一个或多个Set,每个Set中的Drive分布在不同位置。一个对象存储在一个Set上.(for example:{1…64} is divided into 4 sets each of size 16)
    • 一个对象存储在一个Set上
    • 一个集群划分为多个Set
    • 一个Set包含的Drive数量是固定的,默认由系统根据集群规模自动计算得出
    • 一个Set中我的Drive尽可能分布在不同的节点上
3.1 创建用户,创建桶

可以在minio控制面板进行用户的创建以及存储桶(bucket)的创建。我们创建一个test的桶以及创建一个用户并赋予读写的权限

请添加图片描述

请添加图片描述

3.2 添加application.yml配置文件
minio:
  url: http://xxxxxxx #Minio服务所在地址
  bucketName: xxxxxx #存储桶名称
  accessKey: testUser #创建用户访问的key 
  secretKey: 000000000 #创建用户 访问的秘钥
3.3 引入配置

创建MinioConfig 配置文件,将MinioClient 注入容器

@Data
@Configuration
@ConfigurationProperties(prefix = "minio")
public class MinioConfig {

    /**
     * 服务地址
     */
    private String url;

    /**
     * 用户名
     */
    private String accessKey;

    /**
     * 密码
     */
    private String secretKey;

    /**
     * 存储桶名称
     */
    private String bucketName;

    /**
     * 预览到期时间(小时)
     */
    private Integer previewExpiry;



    @Bean
    public MinioClient getMinIOClient() {
        return MinioClient.builder().endpoint(url).credentials(accessKey, secretKey).build();
    }
}
3.4 引入相关操作

创建MinioCosManger文件 封装了minio客户端的一些操作

@Component
@Slf4j
public class MinioCosManger {
    @Autowired
    private MinioConfig prop;

    @Resource
    private MinioClient minioClient;

    /**
     * 查看存储bucket是否存在
     *
     * @return boolean
     */
    public Boolean bucketExists(String bucketName) {
        Boolean found;
        try {
            found = minioClient.bucketExists(BucketExistsArgs.builder().bucket(bucketName).build());
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
        return found;
    }

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

    /**
     * 删除存储bucket
     *
     * @return Boolean
     */
    public Boolean removeBucket(String bucketName) {
        try {
            minioClient.removeBucket(RemoveBucketArgs.builder()
                    .bucket(bucketName)
                    .build());
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
        return true;
    }

    /**
     * 获取全部bucket
     */
    public List<Bucket> getAllBuckets() {
        try {
            List<Bucket> buckets = minioClient.listBuckets();
            return buckets;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }


    /**
     * 文件上传
     *
     * @param file 文件
     * @return Boolean
     */
    public String upload(MultipartFile file) {
        String originalFilename = file.getOriginalFilename();
        if (StringUtils.isBlank(originalFilename)) {
            throw new RuntimeException();
        }
        String fileName = UUID.randomUUID() + originalFilename.substring(originalFilename.lastIndexOf("."));
        String dateFormat = "yyyy-MM/dd";
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern(dateFormat);
        LocalDate nowDate = LocalDate.now();
        String format = nowDate.format(formatter);
        String objectName = format + "/" + fileName;
        try {
            PutObjectArgs objectArgs = PutObjectArgs.builder().bucket(prop.getBucketName()).object(objectName)
                    .stream(file.getInputStream(), file.getSize(), -1).contentType(file.getContentType()).build();
            //文件名称相同会覆盖
            minioClient.putObject(objectArgs);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
        return objectName;
    }

    /**
     * 预览图片
     *
     * @param fileName
     * @return
     */
    public String preview(String fileName) {
        // 查看文件地址
        GetPresignedObjectUrlArgs build = new GetPresignedObjectUrlArgs().builder().bucket(prop.getBucketName()).object(fileName).method(Method.GET).build();
        try {
            String url = minioClient.getPresignedObjectUrl(build);
            return url;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 文件下载
     *
     * @param fileName 文件名称
     * @param res      response
     * @return Boolean
     */
    public void download(String fileName, HttpServletResponse res) {
        GetObjectArgs objectArgs = GetObjectArgs.builder().bucket(prop.getBucketName())
                .object(fileName).build();
        try (GetObjectResponse response = minioClient.getObject(objectArgs)) {
            byte[] buf = new byte[1024];
            int len;
            try (FastByteArrayOutputStream os = new FastByteArrayOutputStream()) {
                while ((len = response.read(buf)) != -1) {
                    os.write(buf, 0, len);
                }
                os.flush();
                byte[] bytes = os.toByteArray();
                res.setCharacterEncoding("utf-8");
                // 设置强制下载不打开
                // res.setContentType("application/force-download");
                res.addHeader("Content-Disposition", "attachment;fileName=" + fileName);
                try (ServletOutputStream stream = res.getOutputStream()) {
                    stream.write(bytes);
                    stream.flush();
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 查看文件对象
     *
     * @return 存储bucket内文件对象信息
     */
    public List<Item> listObjects() {
        Iterable<Result<Item>> results = minioClient.listObjects(
                ListObjectsArgs.builder().bucket(prop.getBucketName()).build());
        List<Item> items = new ArrayList<>();
        try {
            for (Result<Item> result : results) {
                items.add(result.get());
            }
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
        return items;
    }

    /**
     * 删除
     *
     * @param fileName
     * @return
     * @throws Exception
     */
    public boolean remove(String fileName) {
        try {
            minioClient.removeObject(RemoveObjectArgs.builder().bucket(prop.getBucketName()).object(fileName).build());
        } catch (Exception e) {
            return false;
        }
        return true;
    }

}
3.5 创建controller进行测试
/**
 * @version 1.0
 * @Author jerryLau
 * @Date 2024/7/1 11:23
 * @注释
 */
@Api(tags = "文件相关接口")
@Slf4j
@RestController
@RequestMapping(value = "product/file")
public class FileController2 {


    @Autowired
    private MinioCosManger minioUtil;
    @Autowired
    private MinioConfig prop;

    @ApiOperation(value = "查看存储bucket是否存在")
    @GetMapping("/bucketExists")
    public BaseResponse<String> bucketExists(@RequestParam("bucketName") String bucketName) {
        if (minioUtil.bucketExists(bucketName)) {
            return ResultUtils.success("bucketName is exit!");
        } else
            return ResultUtils.error(ErrorCode.NOT_FOUND_ERROR, "bucketName is not exit!");
    }

    @ApiOperation(value = "创建存储bucket")
    @GetMapping("/makeBucket")
    public BaseResponse<String> makeBucket(String bucketName) {
        if (minioUtil.makeBucket(bucketName)) {
            return ResultUtils.success("create bucket success!");
        } else
            return ResultUtils.error(ErrorCode.OPERATION_ERROR, "create bucket error!");

    }

    @ApiOperation(value = "删除存储bucket")
    @GetMapping("/removeBucket")
    public BaseResponse<String> removeBucket(String bucketName) {
        if (minioUtil.removeBucket(bucketName)) {
            return ResultUtils.success("removeBucket  success!");
        } else
            return ResultUtils.error(ErrorCode.OPERATION_ERROR, "removeBucket error!");
    }

    @ApiOperation(value = "获取全部bucket")
    @GetMapping("/getAllBuckets")
    public BaseResponse<List<Bucket>> getAllBuckets() {
        List<Bucket> allBuckets = minioUtil.getAllBuckets();
        return ResultUtils.success(allBuckets);
    }

    @ApiOperation(value = "文件上传返回url")
    @PostMapping("/upload")
    public BaseResponse<String> upload(@RequestParam("file") MultipartFile file) {
        String objectName = minioUtil.upload(file);
        if (null != objectName) {
            String url = (prop.getUrl() + "/" + prop.getBucketName() + "/" + objectName);
            return ResultUtils.success(url);
        }
        return ResultUtils.error(ErrorCode.OPERATION_ERROR, "upload error!");
    }

    @ApiOperation(value = "图片/视频预览")
    @GetMapping("/preview")
    public BaseResponse<String> preview(@RequestParam("fileName") String fileName) {
        String preview = minioUtil.preview(fileName);
        return ResultUtils.success(preview);
    }

    @ApiOperation(value = "文件下载")
    @GetMapping("/download")
    public void download(@RequestParam("fileName") String fileName, HttpServletResponse res) {
        minioUtil.download(fileName, res);
    }

    @ApiOperation(value = "删除文件", notes = "根据url地址删除文件")
    @PostMapping("/delete")
    public BaseResponse<String> remove(String url) {
        String objName = url.substring(url.lastIndexOf(prop.getBucketName() + "/") + prop.getBucketName().length() + 1);
        boolean remove = minioUtil.remove(objName);
        if (remove) {
            return ResultUtils.success(objName + "delete success!");
        } else return ResultUtils.error(ErrorCode.OPERATION_ERROR, objName + "delete error!");
    }

}
3.6 接口测试以及存储验证

通过knife4j或者其他请求测试工具(postman、apifox等),测试接口

请添加图片描述

注意:

1、按照理论来说在上传结束后返回的这个文件的url应该没有办法直接访问,应该在访问该存储对象的时候,去调用preview方法,但是对本人而言,调用preview返回的地址太长了,并且存在一定的时效性,在超过一段时间后将不会在被访问到,所以本人通过给bucket设置access prefix为 readandwrite,这样一来,上传接口的返回url便可直接被访问到了。

2、如果想去调用preview 或者download 方法时,所传入的文件名一定是bucket后面的全部文件名称,比如上面测试图片中,如果调用,传入文件名应为2024-07/01/0011c366-f2a4-4b26-adbc-931d444d7205.png 而不是简单的0011c366-f2a4-4b26-adbc-931d444d7205.png ,否则即使返回了preview的url ,这个url也无法被访问到。

请添加图片描述


dockercomposeminio___504">至此,通过docker-compose手动搭建minio 对象存储服务器已全部完结,喜欢的观众老爷,请一键三连 🎉🎉🎉,感谢大家~


http://www.niftyadmin.cn/n/5537201.html

相关文章

【vue3|第15期】Vue3模板语法入门指南

日期:2024年7月2日 作者:Commas 签名:(ง •_•)ง 积跬步以致千里,积小流以成江海…… 注释:如果您觉得有所帮助,帮忙点个赞,也可以关注我,我们一起成长;如果有不对的地方,还望各位大佬不吝赐教,谢谢^ - ^ 1.01365 = 37.7834;0.99365 = 0.0255 1.02365 = 1377.4083…

【日志分析】退出插件界面时无法卸载插件,错误解决方案pm clear 引起内存泄漏

问题描述 期望行为&#xff1a;退出插件时&#xff08;即不使用时&#xff09;将插件卸载——这样才满足插件化需求。 问题&#xff1a;为了解决插件不正常卸载的问题 &#xff0c;在断连的时候强制pm clear 插件会导致宿主也自动关闭了&#xff0c;进而引起内存泄漏。 错误…

【Linux】服务器被work32病毒入侵CPU占用99%

文章目录 一、问题发现二、问题解决2.1 清楚病毒2.2 开启防火墙2.3 修改SSH端口2.4 仅使用凭据登录&#xff08;可选&#xff09; 一、问题发现 我的一台海外服务器&#xff0c;一直只运行一项服务&#xff08;你懂的&#xff09;&#xff0c;但是前不久我发现CPU占用99%。没在…

input调用手机摄像头实现拍照功能vue

项目需要一个拍照功能&#xff0c;实现功能如下图所示:若使用浏览器则可以直接上传图片&#xff0c;若使用手机则调用手机摄像头拍照。 1.代码结构 <!--input标签--> <input ref"photoRef"type"file"accept"image/*"capture"envir…

efibootmgr 命令及其用途

efibootmgr 是一个在基于 UEFI 的系统上用于管理 EFI 引导项的工具。它允许用户查看、创建、删除和修改 EFI 引导项。以下是一些常见的 efibootmgr 命令及其用途&#xff1a; 查看当前引导项 sudo efibootmgr这个命令会列出所有当前配置的 EFI 引导项。 添加新的引导项 假设…

在C++中内存泄露的几种情况及解决内存泄露和指针越界有哪些方法?

一、在C中&#xff0c;内存泄露通常指的是程序在动态分配内存后未能正确地释放这些内存&#xff0c;导致系统资源被持续占用而无法被其他程序或该程序的后续部分使用。以下是C中内存泄露的几种常见情况&#xff0c;按照不同的原因进行分类和归纳&#xff1a; 忘记释放内存&…

海外金融机构银行保险证券数字化转型营销销售数字化成功案例讲师培训师讲授开户销售营销客户AI人工智能创新思维

金融机构需要数字营销的主要原因 数字银行、直接存款和移动网络的兴起让客户无需前往当地分行即可轻松办理银行业务。这些举措不仅提升了用户体验&#xff0c;也迫使银行向数字化世界迈进。 金融服务公司需要在数字营销渠道上保持稳固的地位&#xff0c;以免落后于大型机构。…

【高考】【填志愿】分数限制下,选好专业还是选好学校?

【高考】选专业时&#xff0c;应避免的误区-CSDN博客 【高考】选专业时以什么为主&#xff1f;-CSDN博客 分数限制下&#xff0c;选好专业还是选好学校&#xff1f;-CSDN博客 分数限制下&#xff0c;选好专业还是选好学校&#xff1f;-CSDN博客 分数限制下&#xff0c;选好专…