linux搭建minIO对象存储服务,springBoot整合

news2025/1/15 17:40:33

minIO 服务搭建

1. 创建安装目录

mkdir -p /usr/local/minio

2. 进入安装目录

cd /usr/local/minio

3.下载安装包 (wget 如果下载太慢,可以手动下载并上传安装包)

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

在这里插入图片描述

4.创建数据存储文件夹

mkdir -p /usr/local/minio/data

5.编辑配置文件

vim /etc/default/minio

5.1 添加内容:用户名、密码、数据存储文件、程序链接端口、web界面访问端口

MINIO_ROOT_USER="minio"
MINIO_ROOT_PASSWORD="minio@123"
MINIO_VOLUMES=" /usr/local/minio/data"
MINIO_OPTS="--address 0.0.0.0:9000"
MINIO_OPTS1="--console-address 0.0.0.0:19001"

6.添加开机自启动配置

编辑配置文件文件

vim /etc/systemd/system/minio.service

添加配置文件内容 (主意启动器路径,自定义服务配置文件路径,以及连接端口要与配置文件里的对应)

[Unit]
Description=MinIO
Documentation=https://docs.min.io
Wants=network-online.target
After=network-online.target
AssertFileIsExecutable=/usr/local/minio/minio

[Service]
WorkingDirectory=/usr/local/minio
ProtectProc=invisible

EnvironmentFile=/etc/default/minio
ExecStartPre=/bin/bash -c "if [ -z \"${MINIO_VOLUMES}\" ]; then echo \"Variable MINIO_VOLUMES not set in /etc/default/minio\"; exit 1; fi"
ExecStart=/usr/local/minio/minio server --address 0.0.0.0:9000 $MINIO_OPTS  $MINIO_OPTS1 $MINIO_VOLUMES

# Let systemd restart this service always
Restart=always

# Specifies the maximum file descriptor number that can be opened by this process
LimitNOFILE=65536

# Specifies the maximum number of threads this process can create
TasksMax=infinity

# Disable timeout logic and wait until process is stopped
TimeoutStopSec=infinity
SendSIGKILL=no

[Install]
WantedBy=multi-user.target

7. 启动服务

#加载配置
systemctl daemon-reload
#添加启动器权限
chmod +x /usr/local/minio/minio
#启动minio
systemctl start minio.service
#开启开机自启
systemctl enable minio.service

查看服务

ps -ef |grep minio

在这里插入图片描述

8.验证服务

http://ip:19001/login

在这里插入图片描述
通过配置的账号密码登录
在这里插入图片描述

9.常用命令

systemctl start minio.service   #启动minio服务
systemctl stop minio.service   #停止minio服务
systemctl restart minio.service   #重新启动服务
systemctl status minio.service   #查看服务当前状态
systemctl enable minio.service   #设置开机自启动
systemctl disable minio.service   #停止开机自启动

10.目录

安装目录:/usr/local/minio
数据保存目录:/usr/local/minio/data
配置文件目录:/etc/default/minio
启动配置文件存放目录:vim /etc/systemd/system/minio.service

SpringBoot 整合

1.添加依赖

比较新的版本 最新到8.5.5 ,更多版本可以到maven仓库找
https://mvnrepository.com/artifact/io.minio/minio

        <!-- https://mvnrepository.com/artifact/io.minio/minio -->
        <dependency>
            <groupId>io.minio</groupId>
            <artifactId>minio</artifactId>
            <version>8.5.3</version>
        </dependency>

2.添加yml配置

minio:
  endpoint: http://127.0.0.1:9000 #Minio服务所在地址
  bucketName: test #存储桶名称
  accessKey: minioadmin #访问的key
  secretKey: minioadmin #访问的秘钥

3.MinioConfig.class配置类

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

    private String endpoint;
    private String accessKey;
    private String secretKey;
    private String bucketName;

    @Bean
    public MinioClient minioClient() {
        return MinioClient.builder()
                .endpoint(endpoint)
                .credentials(accessKey, secretKey)
                .build();
    }
}

4.minio工具类

import com.alibaba.nacos.common.utils.UuidUtils;
import com.system.common.utils.RandomIdUtil;
import com.system.iotmanagement.config.MinioConfig;
import io.minio.*;
import io.minio.http.Method;
import io.minio.messages.Bucket;
import io.minio.messages.Item;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.util.FastByteArrayOutputStream;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.util.UriUtils;

import javax.annotation.Resource;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;

@Component
@Slf4j
public class MinioUtil {
    @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 {
           if (!bucketExists(bucketName)) {
               minioClient.makeBucket(MakeBucketArgs.builder()
                       .bucket(bucketName)
                       .build());
               String policyJson = "{\n" +
                       "\t\"Version\": \""+new SimpleDateFormat("yyyy-mm-dd").format(System.currentTimeMillis())+"\",\n" +
                       "\t\"Statement\": [{\n" +
                       "\t\t\"Effect\": \"Allow\",\n" +
                       "\t\t\"Principal\": {\n" +
                       "\t\t\t\"AWS\": [\"*\"]\n" +
                       "\t\t},\n" +
                       "\t\t\"Action\": [\"s3:GetBucketLocation\", \"s3:ListBucket\", \"s3:ListBucketMultipartUploads\"],\n" +
                       "\t\t\"Resource\": [\"arn:aws:s3:::" + bucketName + "\"]\n" +
                       "\t}, {\n" +
                       "\t\t\"Effect\": \"Allow\",\n" +
                       "\t\t\"Principal\": {\n" +
                       "\t\t\t\"AWS\": [\"*\"]\n" +
                       "\t\t},\n" +
                       "\t\t\"Action\": [\"s3:AbortMultipartUpload\", \"s3:DeleteObject\", \"s3:GetObject\", \"s3:ListMultipartUploadParts\", \"s3:PutObject\"],\n" +
                       "\t\t\"Resource\": [\"arn:aws:s3:::" + bucketName + "/*\"]\n" +
                       "\t}]\n" +
                       "}\n";
               minioClient.setBucketPolicy(SetBucketPolicyArgs.builder().bucket(bucketName).config(policyJson).build());
               log.info("buckets:【{}】,创建[readwrite]策略成功!", bucketName);
           } else {
               log.info("minio bucket->>>【{}】already exists", bucketName);
           }
       } 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 = UuidUtils.generateUuid() + originalFilename.substring(originalFilename.lastIndexOf("."));
        String objectName = RandomIdUtil.getyyyyMMddString() + "/" + 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=" + UriUtils.encode(fileName, StandardCharsets.UTF_8));
                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;
    }
}

生成时间字符串的工具类,(可以自己另外引用其他的)

import cn.hutool.crypto.SecureUtil;
import cn.hutool.crypto.symmetric.AES;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Random;

public class RandomIdUtil {

    /**
     * 生成随机ID:当前年月日时分秒毫秒 +五位随机数
     */
    public static String getRandomID(String businessCode) {
        String str  = new SimpleDateFormat("yyyyMMddHHmmssSS").format(new Date());
        Random random = new Random();
        int rannum = (int) (random.nextDouble() * (99999 - 10000 + 1)) + 10000;// 获取5位随机数
        return businessCode+str + rannum;// 当前时间
    }

    public static String getyyyyMMddHHString() {
        return new SimpleDateFormat("yyyyMMddHH").format(new Date());
    }

    public static String getyyyyMMddString() {
        return new SimpleDateFormat("yyyyMMdd").format(new Date());
    }

}

5. 文件处理接口



@Api(tags = "文件相关接口")
@Slf4j
@RestController
@RequestMapping(value = "product/file")
public class FileController {


    @Autowired
    private MinioUtil minioUtil;
    @Autowired
    private MinioConfig prop;

    @ApiOperation(value = "查看存储bucket是否存在")
    @GetMapping("/bucketExists")
    public R bucketExists(@RequestParam("bucketName") String bucketName) {
        return R.ok().put("bucketName",minioUtil.bucketExists(bucketName));
    }

    @ApiOperation(value = "创建存储bucket")
    @GetMapping("/makeBucket")
    public R makeBucket(String bucketName) {
        return R.ok().put("bucketName",minioUtil.makeBucket(bucketName));
    }

    @ApiOperation(value = "删除存储bucket")
    @GetMapping("/removeBucket")
    public R removeBucket(String bucketName) {
        return R.ok().put("bucketName",minioUtil.removeBucket(bucketName));
    }

    @ApiOperation(value = "获取全部bucket")
    @GetMapping("/getAllBuckets")
    public R getAllBuckets() {
        List<Bucket> allBuckets = minioUtil.getAllBuckets();
        return R.ok().put("allBuckets",allBuckets);
    }

    @ApiOperation(value = "文件上传返回url")
    @PostMapping("/upload")
    public R upload(@RequestParam("file") MultipartFile file) {
        String objectName = minioUtil.upload(file);
        if (null != objectName) {
            return R.ok().put("url",(prop.getEndpoint() + "/" + prop.getBucketName() + "/" + objectName));
        }
        return R.error();
    }

    @ApiOperation(value = "图片/视频预览")
    @GetMapping("/preview")
    public R preview(@RequestParam("fileName") String fileName) {
        return R.ok().put("filleName",minioUtil.preview(fileName));
    }

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

    @ApiOperation(value = "删除文件", notes = "根据url地址删除文件")
    @PostMapping("/delete")
    public R remove(String url) {
        String objName = url.substring(url.lastIndexOf(prop.getBucketName()+"/") + prop.getBucketName().length()+1);
        minioUtil.remove(objName);
        return R.ok().put("objName",objName);
    }

}

基本操作和可能遇到的问题

1.存储bucket 的两种方式

1.客户端手动创建
在这里插入图片描述
2.代码创建,见工具类和接口方法

可能遇到的问题

当文件上传之后比如一张图片,直接通过图片路径访问报一下错误
在这里插入图片描述
这是因为浏览器打开没有权限
解决办法,默认显示的是n/a,设置minio代理权限,选择对应的桶,将其代理权限也设置为公有
在这里插入图片描述
就可以成功访问了

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/926916.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

gradle 环境配置

环境变量 # gradle 构建工具目录 export GRADLE_HOME/Users/tangjianghua5/software/gradle-6.0 export PATH$PATH:$GRADLE_HOME/bin # gradle 本地仓库目录 export GRADLE_USER_HOME/Users/tangjianghua5/.m2/repository export PATH$PATH:$GRADLE_USER_HOME全局配置 init.gr…

【LeetCode】437.路径总和Ⅲ

题目 给定一个二叉树的根节点 root &#xff0c;和一个整数 targetSum &#xff0c;求该二叉树里节点值之和等于 targetSum 的 路径 的数目。 路径 不需要从根节点开始&#xff0c;也不需要在叶子节点结束&#xff0c;但是路径方向必须是向下的&#xff08;只能从父节点到子节…

继承抽象类习题答案

基础题目 第一题&#xff1a;概念辨析 什么叫做类与类的继承&#xff0c;作用是什么&#xff1f; 就是子类继承父类的属性和行为&#xff0c;使得子类对象具有与父类相同的属性、相同的行为。作用是子类复用父类内容。 继承后&#xff0c;父类与子类之间&#xff0c;各成员有…

融合CDN 如何有效的抵抗DDoS攻击

绝大部分对外网站所有者都离不开CDN的支持&#xff0c;据统计&#xff0c;全球高达70%的互联网流量都是通过CDN来进行缓存和加速的&#xff0c;不论是国外知名的CDN厂商&#xff1a;如Cloudflare、AWS、Akamai等&#xff0c;还是国内主流的CDN厂商阿里云华为云腾讯云等&#xf…

vue 实现word文档预览和下载 docx-preview 和 vue-office

1.先下载引入 npm i docx-preview --save import { renderAsync } from docx-preview;2.使用 fetch(url) .then((response) > {let docData response.blob(); //将文件转换成bolb形式//选择要渲染的元素let childRef document.getElementsByClassName("childRef&qu…

【ulimit 命令】LINUX单进程能够打开的最大文件句柄数

Linux系统如何查看修改最大打开文件数,这个问题对于很多刚刚学习linux的小伙伴来说觉得有点奇怪。Linux系统和windows不同之处在于更强的多任务多线程,由于文件系统结构的不同linux针对不同进程不同用户都可以设置最打打开文件数。 查看当前系统最大的文件打开数,如下图 在…

5165. CCC单词搜索

题目链接&#xff1a;https://www.acwing.com/problem/content/5168/ 题目&#xff1a; 给定一个 RC 的大写字母矩阵。 请你在其中寻找目标单词 W。 已知&#xff0c;目标单词 W 由若干个不同的大写字母构成。 目标单词可以遵循以下两种规则&#xff0c;出现在矩阵的水平、…

c++11 标准模板(STL)(std::basic_istringstream)(四)

定义于头文件 <sstream> template< class CharT, class Traits std::char_traits<CharT> > class basic_istringstream;(C11 前)template< class CharT, class Traits std::char_traits<CharT>, class Allocator std::allo…

电脑桌面日历提醒怎么设置?设置日期提醒的简单方法

在我们使用电脑办公或学习时&#xff0c;为了避免忘记重要事项&#xff0c;例如会议、截止日期等&#xff0c;我们常常希望能在电脑桌面设置日历提醒。一般来说&#xff0c;电脑系统自带的日历上可以新增日程并设置提醒日期。那么电脑桌面日历提醒怎么设置呢&#xff1f; 很多人…

RocketMQ-(7-1)-可观测-Metrics

RocketMQ 以 Prometheus 格式公开以下指标。您可以使用这些指标监视您的集群。 服务端 Metrics 指标生产者 Metrics 指标消费者 Metrics 指标 版本支持&#xff1a;以下指标 Metrics 是从 5.1.0 版本开始支持。 Metrics 指标详情​ Metric types​ 消息队列 RocketMQ 版定义…

微调llama2模型教程:创建自己的Python代码生成器

本文将演示如何使用PEFT、QLoRa和Huggingface对新的lama-2进行微调&#xff0c;生成自己的代码生成器。所以本文将重点展示如何定制自己的llama2&#xff0c;进行快速训练&#xff0c;以完成特定任务。 一些知识点 llama2相比于前一代&#xff0c;令牌数量增加了40%&#xff0…

CentOS7.9安装Java11

文章目录 Java11版本介绍安装步骤查看并卸载已有版本安装Java11最新版本配置生效 openjdk介绍 Java11版本介绍 Java 11是Java编程语言的一个重要版本&#xff0c;于2018年9月发布Java 11在语言特性、性能优化和安全性方面都有一些显著的改进&#xff0c;为Java开发者提供了更多…

.NetCore之log4net的使用

1.首先下载log4ne的包&#xff1a; 2.添加配置文件log4net.config <?xml version"1.0" encoding"utf-8" ?> <configuration><!-- This section contains the log4net configuration settings --><log4net><appender name&q…

初探函数式编程---以Map/Reduce/Filter为例

如函数式编程--酷壳[1] 总结&#xff0c; 函数式编程的三大特性; 数据不可变性 函数作为一等公民(函数可以像变量一样来创建/修改/传递 等) 尾递归优化(重用stack,减轻栈的压力) 函数式编程用到的几个技术&#xff1a; 函数式编程的理念&#xff1a;把函数当成变量来用&#xf…

亚马逊电咖啡壶UL1082测试标准

UL1082标准是适用于额定电压为120V,按照国家电气编码进行使用的便携式电咖啡壶&#xff0c;咖啡渗漏壶及其它酿造类器具&#xff0c;除了咖啡壶外&#xff0c;本标准也适用于荼壶、水煲、玻璃水煲、汤保温壶及其它类似器具。这些器具都具有以下特点&#xff1a; &#xff08;1…

Day06-Vue全家桶项目

Day01-Vue全家桶项目 一 全家桶项目介绍 Vue在使用脚手架创建项目的时候,提供前端工程化项目 目前主要学习了Vue基础:Vue指令、Vue组件开发、Vue样式、组件通信、生命周期 全家桶项目是很多技术结合的一种开发模式: 全家桶项目搭建路由搭建(前端路由)网络请求封装Elem…

【Java从0到1学习】11 Java集合框架

1. Collection 1.1 Java类中集合的关系图 1.2 集合类概述 在程序中可以通过数组来保存多个对象&#xff0c;但在某些情况下开发人员无法预先确定需要保存对象的个数&#xff0c;此时数组将不再适用&#xff0c;因为数组的长度不可变。例如&#xff0c;要保存一个学校的学生信…

华为数通方向HCIP-DataCom H12-821题库(单选题:81-100)

第81题 某公司新购入一台网络设备,作为网络管理员,初次配置该设备通常通过什么方式? A、FTP B、Telnet C、SNMP D、Console 口登录 答案: D 解析&#xff1a; 通常情况下&#xff0c;初次配置网络设备会通过Console口登录的方式进行。Console口是一种串口接口&#xff0c…

网络安全工程师岗位一览-徐庆臣(黑客洗白者)

安全服务工程师 安全运维工程师 渗透测试工程师 Web安全工程师 安全攻防工程师 等保测评工程师 …… 代码审计工程师 威胁分析工程师 无线安全工程师 安全研发工程师 移动安全工程师 云计算安全工程师 ……

Socket通信与WebSocket协议

文章目录 目录 文章目录 前言 一、Socket通信 1.1 BIO 1.2 NIO 1.3 AIO 二、WebSocket协议 总结 前言 一、Socket通信 Socket是一种用于网络通信的编程接口&#xff08;API&#xff09;&#xff0c;它提供了一种机制&#xff0c;使不同主机之间可以通过网络进行数据传输和通信…