MinIO使用基础教程
MinIO使用基础教程
一、背景
对于网站系统,若为降低成本投入,将文件存储服务和网站系统部署在同一台服务器中,访问量不大,基本不会有问题,但访问量逐渐升高,网站文件资源读取逐渐频繁,单机服务器可能难以承载较大的请求量,可能会出现网站打不开,甚至系统异常等问题。
解决方案:采用云存储服务,将访问很频繁的文件资源服务,由本地改成云厂商提供的文件存储服务,比如阿里云 OSS、七牛云、腾讯云、百度云等等,迁移之后,网站的访问压力会得到极大的释放,服务也会变得更加稳定。
但是,这些云存储服务大部分都是收费的,以阿里云为例,数据存储通常按照 0.12 元/GB/月的标准来收费,日积月累也是一笔巨款。
采用免费开源的 fastDFS 工具来作为文件存储服务器,虽然性能不错,但软件安装环境非常复杂,且没有完整的技术文档,大部分都是公司或者网友自己总结的文档,维护起来非常困难。
直到 MinIO应运而生,云存储服务工具便多一个新的可选项。
MinIO 是一款号称世界上速度最快的对象存储服务,专为大规模数据存储和分析而设计。支持在各种环境中部署,包括物理服务器、虚拟机、容器等,最关键的是它的技术文档非常完善,非常容易上手;同时,对个人用户是完全开源免费的。
二、快速安装
2.1 虚拟机安装
docker启动minio命令:
docker run \
-p 9000:9000 \
-p 9001:9001 \
--name minio1 \
-v D:\minio\data:/data \
-e "MINIO_ROOT_USER=ROOTUSER" \
-e "MINIO_ROOT_PASSWORD=CHANGEME123" \
quay.io/minio/minio server /data --console-address ":9001"
相关参数解读:
docker run:表示启动运行容器
-p:表示为容器绑定一个本地的端口
-name:表示为容器创建一个本地的名字
-v:表示将文件路径设置为容器使用的持久卷位置。当 MinIO 将数据写入 /data时,该数据会镜像到本地路径~/minio/data, 使其能够在容器重新启动时保持持久化。您可以设置任何具有读取、写入和删除权限的文件路径来使用。
-e:表示设置登陆控制台的用户名和密码。其中控制台的访问地址为http://本机ip:9001,api 的访问地址为http://本机ip:9000。
2.2 Windows安装
2.2.1 下载MinIO服务器
下载地址:
https://dl.minio.org.cn/server/minio/release/windows-amd64/minio.exe
ps:不能双击文件来运行,下一步包括运行可执行文件的指令。
2.2.2 启动 MinIO Server
在PowerShell或命令提示符中,cd命令进到可执行文件的位置,或添加minio.exe文件所在路径 至windows系统环境变量 $PATH 中。
假设存放路径为:D:\tool\minio\minio.exe,就需要:cd D:\tool\minio,
然后执行命令:
.\minio.exe server D:\tool\minio --console-address :9090
控制台窗口会输出打印内容:
API: http://192.0.2.10:9000 http://127.0.0.1:9000
RootUser: minioadmin
RootPass: minioadmin
Console: http://192.0.2.10:9090 http://127.0.0.1:9090
RootUser: minioadmin
RootPass: minioadmin
Command-line: https://minio.org.cn/docs/minio/linux/reference/minio-mc.html
$ mc alias set myminio http://192.0.2.10:9000 minioadmin minioadmin
Documentation: https://minio.org.cn/docs/minio/linux/index.html
WARNING: Detected default credentials 'minioadmin:minioadmin', we recommend that you change these values with 'MINIO_ROOT_USER' and 'MINIO_ROOT_PASSWORD' environment variables.
该服务与当前PowerShell 或命令提示符窗口相绑定。 关闭窗口将停止服务器并结束该服务。
2.2.3 通过浏览器访问MinIO服务控制台
访问 MinIO控制台:
http://127.0.0.1:9090
MinIO使用监听的端口为 9000 端口,这个端口使用API与MinIO服务器进行通信和进行SDK调用,通过浏览器访问 9000 端口会自动跳转至MinIO控制台。
登录MinIO 控制台可以使用默认的 Root用户名/密码 登录: minioadmin / minioadmin 。
就可以显示相关控制台的详细信息。
三、使用介绍
在对象存储服务里面,所有的文件都是以桶的形式来组织的。简单说,可以将桶看作是目录,这个目录下有很多的文件或者文件夹,这和其它云存储服务基本一致。
3.1 创建存储桶
所有的文件必须要存储到桶中,因此需要先创建存储桶。
若要修改存储桶信息,点击左侧的Buckets菜单,即可展示存储桶配置信息。
3.2 上传和下载文件
点击Object Browser菜单,可看到刚刚创建的存储桶public-bucket,点击进入,上传想要存储的文件。
若想下载文件或者预览文件,点击文件,右侧会弹出相关的操作按钮,点击相应的操作按钮即可。
3.3 设置文件公开访问
默认创建的存储桶,均为私有桶,无法被公开访问。
以 api 方式直接访问,会提示无权限:
127.0.0.1/9000/public-bucket/sso流程图.png
通常而言,要将数据写入操作进行控制;而读操作,很多不涉及安全问题,希望能被互联网公开访问,以便加快文件的访问速度。
可以在存储桶里面配置,将数据读取权限设置为公开访问:
再次访问:
此时文件可以公开访问。
四、实战SpringBoot + Minio实现文件上传和查询
引入依赖:
<!-- 操作minio的java客户端-->
<dependency>
<groupId>io.minio</groupId>
<artifactId>minio</artifactId>
<version>8.5.2</version>
</dependency>
<!-- 操作minio的java客户端-->
<dependency>
<groupId>io.minio</groupId>
<artifactId>minio</artifactId>
<version>8.2.1</version>
</dependency>
<!-- jwt鉴权相应依赖-->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.3</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<version>0.11.2</version>
</dependency>
获取API访问凭证:
编写配置文件:
server:
port: 8080
spring:
servlet:
multipart:
max-file-size: 10MB
max-request-size: 10MB
#minio配置
minio:
access-key: dAMaxkWaXUD1CV1JHbqw
secret-key: AXt3SD0JFkDENFbMeJKOOQb5wj8KvabZWu33Rs84
url: http://192.168.18.14:9090 #访问地址
bucket-name: public-bucket
创建Minio的配置类:
import io.minio.MinioClient;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Data
@Configuration
@ConfigurationProperties(prefix = "spring.minio")
public class MinioConfig {
private String accessKey;
private String secretKey;
private String url;
private String bucketName;
@Bean
public MinioClient minioClient(){
return MinioClient.builder()
.endpoint(url)
.credentials(accessKey,secretKey)
.build();
}
}
创建Minio的工具类:
import com.xiaohui.config.MinioConfig;
import io.minio.*;
import io.minio.errors.*;
import io.minio.http.Method;
import lombok.SneakyThrows;
import org.apache.tomcat.util.http.fileupload.IOUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.time.ZonedDateTime;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
/**
* @Description Minio工具类
*/
@Component
public class MinioUtils {
@Autowired
private MinioClient minioClient;
@Autowired
private MinioConfig configuration;
/**
* @param name 名字
* @Description description: 判断bucket是否存在,不存在则创建
*/
public boolean existBucket(String name) {
boolean exists;
try {
exists = minioClient.bucketExists(BucketExistsArgs.builder().bucket(name).build());
if (!exists) {
minioClient.makeBucket(MakeBucketArgs.builder().bucket(name).build());
exists = true;
}
} catch (Exception e) {
e.printStackTrace();
exists = false;
}
return exists;
}
/**
* @param bucketName 存储bucket名称
* @Description 创建存储bucket
*/
public Boolean makeBucket(String bucketName) {
try {
minioClient.makeBucket(MakeBucketArgs.builder()
.bucket(bucketName)
.build());
} catch (Exception e) {
e.printStackTrace();
return false;
}
return true;
}
/**
* @param bucketName 存储bucket名称
* @Description 删除存储bucket
*/
public Boolean removeBucket(String bucketName) {
try {
minioClient.removeBucket(RemoveBucketArgs.builder()
.bucket(bucketName)
.build());
} catch (Exception e) {
e.printStackTrace();
return false;
}
return true;
}
/**
* @param fileName 文件名称
* @param time 时间
* @Description 获取上传临时签名
*/
@SneakyThrows
public Map getPolicy(String fileName, ZonedDateTime time) {
PostPolicy postPolicy = new PostPolicy(configuration.getBucketName(), time);
postPolicy.addEqualsCondition("key", fileName);
try {
Map<String, String> map = minioClient.getPresignedPostFormData(postPolicy);
HashMap<String, String> map1 = new HashMap<>();
map.forEach((k, v) -> {
map1.put(k.replaceAll("-", ""), v);
});
map1.put("host", configuration.getUrl() + "/" + configuration.getBucketName());
return map1;
} catch (ErrorResponseException e) {
e.printStackTrace();
} catch (InsufficientDataException e) {
e.printStackTrace();
} catch (InternalException e) {
e.printStackTrace();
} catch (InvalidKeyException e) {
e.printStackTrace();
} catch (InvalidResponseException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (ServerException e) {
e.printStackTrace();
} catch (XmlParserException e) {
e.printStackTrace();
}
return null;
}
/**
* @param objectName 对象名称
* @param method 方法
* @param time 时间
* @param timeUnit 时间单位
* @Description 获取上传文件的url
*/
public String getPolicyUrl(String objectName, Method method, int time, TimeUnit timeUnit) {
try {
return minioClient.getPresignedObjectUrl(GetPresignedObjectUrlArgs.builder()
.method(method)
.bucket(configuration.getBucketName())
.object(objectName)
.expiry(time, timeUnit).build());
} catch (ErrorResponseException e) {
e.printStackTrace();
} catch (InsufficientDataException e) {
e.printStackTrace();
} catch (InternalException e) {
e.printStackTrace();
} catch (InvalidKeyException e) {
e.printStackTrace();
} catch (InvalidResponseException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (XmlParserException e) {
e.printStackTrace();
} catch (ServerException e) {
e.printStackTrace();
}
return null;
}
/**
* @param file 文件
* @param fileName 文件名称
* @Description 上传文件
*/
public void upload(MultipartFile file, String fileName) {
// 使用putObject上传一个文件到存储桶中。
try {
InputStream inputStream = file.getInputStream();
minioClient.putObject(PutObjectArgs.builder()
.bucket(configuration.getBucketName())
.object(fileName)
.stream(inputStream, file.getSize(), -1)
.contentType(file.getContentType())
.build());
} catch (ErrorResponseException e) {
e.printStackTrace();
} catch (InsufficientDataException e) {
e.printStackTrace();
} catch (InternalException e) {
e.printStackTrace();
} catch (InvalidKeyException e) {
e.printStackTrace();
} catch (InvalidResponseException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (ServerException e) {
e.printStackTrace();
} catch (XmlParserException e) {
e.printStackTrace();
}
}
/**
* @param objectName 对象名称
* @param time 时间
* @param timeUnit 时间单位
* @Description 根据filename获取文件访问地址
*/
public String getUrl(String objectName, int time, TimeUnit timeUnit) {
String url = null;
try {
url = minioClient.getPresignedObjectUrl(GetPresignedObjectUrlArgs.builder()
.method(Method.GET)
.bucket(configuration.getBucketName())
.object(objectName)
.expiry(time, timeUnit).build());
} catch (ErrorResponseException e) {
e.printStackTrace();
} catch (InsufficientDataException e) {
e.printStackTrace();
} catch (InternalException e) {
e.printStackTrace();
} catch (InvalidKeyException e) {
e.printStackTrace();
} catch (InvalidResponseException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (XmlParserException e) {
e.printStackTrace();
} catch (ServerException e) {
e.printStackTrace();
}
return url;
}
/**
* @Description description: 下载文件
*/
public ResponseEntity<byte[]> download(String fileName) {
ResponseEntity<byte[]> responseEntity = null;
InputStream in = null;
ByteArrayOutputStream out = null;
try {
in = minioClient.getObject(GetObjectArgs.builder().bucket(configuration.getBucketName()).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.SUCCESS);
} 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 objectFile 对象文件
*/
public String getFileUrl(String objectFile) {
try {
return minioClient.getPresignedObjectUrl(GetPresignedObjectUrlArgs.builder()
.method(Method.GET)
.bucket(configuration.getBucketName())
.object(objectFile)
.build()
);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}
其中包含的常量
http请求状态:
/**
* @Description http请求状态
*/
public class HttpStatus
{
/**
* 操作成功
*/
public static final int SUCCESS = 200;
/**
* 对象创建成功
*/
public static final int CREATED = 201;
/**
* 请求已经被接受
*/
public static final int ACCEPTED = 202;
/**
* 操作已经执行成功,但是没有返回数据
*/
public static final int NO_CONTENT = 204;
/**
* 资源已被移除
*/
public static final int MOVED_PERM = 301;
/**
* 重定向
*/
public static final int SEE_OTHER = 303;
/**
* 资源没有被修改
*/
public static final int NOT_MODIFIED = 304;
/**
* 参数列表错误(缺少,格式不匹配)
*/
public static final int BAD_REQUEST = 400;
/**
* 未授权
*/
public static final int UNAUTHORIZED = 401;
/**
* 访问受限,授权过期
*/
public static final int FORBIDDEN = 403;
/**
* 资源,服务未找到
*/
public static final int NOT_FOUND = 404;
/**
* 不允许的http方法
*/
public static final int BAD_METHOD = 405;
/**
* 资源冲突,或者资源被锁
*/
public static final int CONFLICT = 409;
/**
* 不支持的数据,媒体类型
*/
public static final int UNSUPPORTED_TYPE = 415;
/**
* 系统内部错误
*/
public static final int ERROR = 500;
/**
* 接口未实现
*/
public static final int NOT_IMPLEMENTED = 501;
/**
* 系统警告消息
*/
public static final int WARN = 601;
}
通用常量信息:
import io.jsonwebtoken.Claims;
/**
* @Description 通用常量信息
*/
public class Constants
{
/**
* UTF-8 字符集
*/
public static final String UTF8 = "UTF-8";
/**
* GBK 字符集
*/
public static final String GBK = "GBK";
/**
* www主域
*/
public static final String WWW = "www.";
/**
* http请求
*/
public static final String HTTP = "http://";
/**
* https请求
*/
public static final String HTTPS = "https://";
/**
* 通用成功标识
*/
public static final String SUCCESS = "0";
/**
* 通用失败标识
*/
public static final String FAIL = "1";
/**
* 登录成功
*/
public static final String LOGIN_SUCCESS = "Success";
/**
* 注销
*/
public static final String LOGOUT = "Logout";
/**
* 注册
*/
public static final String REGISTER = "Register";
/**
* 登录失败
*/
public static final String LOGIN_FAIL = "Error";
/**
* 验证码有效期(分钟)
*/
public static final Integer CAPTCHA_EXPIRATION = 2;
/**
* 令牌
*/
public static final String TOKEN = "token";
/**
* 令牌前缀
*/
public static final String TOKEN_PREFIX = "Bearer ";
/**
* 令牌前缀
*/
public static final String LOGIN_USER_KEY = "login_user_key";
/**
* 用户ID
*/
public static final String JWT_USERID = "userid";
/**
* 用户名称
*/
public static final String JWT_USERNAME = Claims.SUBJECT;
/**
* 用户头像
*/
public static final String JWT_AVATAR = "avatar";
/**
* 创建时间
*/
public static final String JWT_CREATED = "created";
/**
* 用户权限
*/
public static final String JWT_AUTHORITIES = "authorities";
/**
* 资源映射路径 前缀
*/
public static final String RESOURCE_PREFIX = "/profile";
/**
* RMI 远程方法调用
*/
public static final String LOOKUP_RMI = "rmi:";
/**
* LDAP 远程方法调用
*/
public static final String LOOKUP_LDAP = "ldap:";
/**
* LDAPS 远程方法调用
*/
public static final String LOOKUP_LDAPS = "ldaps:";
/**
* 定时任务白名单配置(仅允许访问的包名,如其他需要可以自行添加)
*/
public static final String[] JOB_WHITELIST_STR = { "com.ruoyi" };
/**
* 定时任务违规的字符
*/
public static final String[] JOB_ERROR_STR = { "java.net.URL", "javax.naming.InitialContext", "org.yaml.snakeyaml",
"org.springframework", "org.apache", "com.ruoyi.common.utils.file", "com.ruoyi.common.config" };
}
创建Ajax请求工具类:
/**
* @Description ajax结果
*/
public class AjaxResult extends HashMap<String, Object>
{
private static final long serialVersionUID = 1L;
/** 状态码 */
public static final String CODE_TAG = "code";
/** 返回内容 */
public static final String MSG_TAG = "msg";
/** 数据对象 */
public static final String DATA_TAG = "data";
/**
* 初始化一个新创建的 AjaxResult 对象,使其表示一个空消息。
*/
public AjaxResult()
{
}
/**
* 初始化一个新创建的 AjaxResult 对象
*
* @param code 状态码
* @param msg 返回内容
*/
public AjaxResult(int code, String msg)
{
super.put(CODE_TAG, code);
super.put(MSG_TAG, msg);
}
/**
* 初始化一个新创建的 AjaxResult 对象
*
* @param code 状态码
* @param msg 返回内容
* @param data 数据对象
*/
public AjaxResult(int code, String msg, Object data)
{
super.put(CODE_TAG, code);
super.put(MSG_TAG, msg);
if (data!=null)
{
super.put(DATA_TAG, data);
}
}
/**
* 返回成功消息
*
* @return 成功消息
*/
public static AjaxResult success()
{
return AjaxResult.success("操作成功");
}
/**
* 返回成功数据
*
* @return 成功消息
*/
public static AjaxResult success(Object data)
{
return AjaxResult.success("操作成功", data);
}
/**
* 返回成功消息
*
* @param msg 返回内容
* @return 成功消息
*/
public static AjaxResult success(String msg)
{
return AjaxResult.success(msg, null);
}
/**
* 返回成功消息
*
* @param msg 返回内容
* @param data 数据对象
* @return 成功消息
*/
public static AjaxResult success(String msg, Object data)
{
return new AjaxResult(HttpStatus.SUCCESS, msg, data);
}
/**
* 返回警告消息
*
* @param msg 返回内容
* @return 警告消息
*/
public static AjaxResult warn(String msg)
{
return AjaxResult.warn(msg, null);
}
/**
* 返回警告消息
*
* @param msg 返回内容
* @param data 数据对象
* @return 警告消息
*/
public static AjaxResult warn(String msg, Object data)
{
return new AjaxResult(HttpStatus.WARN, msg, data);
}
/**
* 返回错误消息
*
* @return 错误消息
*/
public static AjaxResult error()
{
return AjaxResult.error("操作失败");
}
/**
* 返回错误消息
*
* @param msg 返回内容
* @return 错误消息
*/
public static AjaxResult error(String msg)
{
return AjaxResult.error(msg, null);
}
/**
* 返回错误消息
*
* @param msg 返回内容
* @param data 数据对象
* @return 错误消息
*/
public static AjaxResult error(String msg, Object data)
{
return new AjaxResult(HttpStatus.ERROR, msg, data);
}
/**
* 返回错误消息
*
* @param code 状态码
* @param msg 返回内容
* @return 错误消息
*/
public static AjaxResult error(int code, String msg)
{
return new AjaxResult(code, msg, null);
}
/**
* 方便链式调用
*
* @param key 键
* @param value 值
* @return 数据对象
*/
@Override
public AjaxResult put(String key, Object value)
{
super.put(key, value);
return this;
}
}
创建Minio文件操作接口层:
import com.xiaohui.utils.AjaxResult;
import com.xiaohui.utils.MinioUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import java.util.HashMap;
/**
* @Description minio文件上传控制器
*/
@CrossOrigin
@RestController
@RequestMapping("/api")
public class MinioFileUploadController {
@Autowired
private MinioUtils minioUtils;
/**
* @param file 文件
* @param fileName 文件名称
* @Description 上传文件
*/
@GetMapping("/upload")
public AjaxResult uploadFile(@RequestParam("file") MultipartFile file, String fileName) {
minioUtils.upload(file, fileName);
return AjaxResult.success("上传成功");
}
/**
* @param fileName 文件名称
* @Description dowload文件
*/
@GetMapping("/dowload")
public ResponseEntity dowloadFile(@RequestParam("fileName") String fileName) {
return minioUtils.download(fileName);
}
/**
* @param fileName 文件名称
* @Description 得到文件url
*/
@GetMapping("/getUrl")
public AjaxResult getFileUrl(@RequestParam("fileName") String fileName){
HashMap map=new HashMap();
map.put("FileUrl",minioUtils.getFileUrl(fileName));
return AjaxResult.success(map);
}
}
五、测试
Minio大文件上传:httpy/localhost:8080/api/upload
Minio大文件查询:http://locahost:8080/api/getUr?fileName=测试
六、小结
minio 用来做个人云存储工具完全是开源免费的,若当前需要一个云存储工具,首推minio。
原文地址:https://blog.csdn.net/qq_40623672/article/details/140634694
免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!