原文地址
MinIO创建桶的名字只支持小写
1. 选择版本
首先去Docker官方仓库查找自己需要的版本,我安装的是minio/minio:RELEASE.2021-10-13T00-23-17Z
docker pull minio/minio:RELEASE.2021-10-13T00-23-17Z
2. 启动
docker run -d --restart=always \
-p 9000:9000 \
-p 9090:9090 \
--name minio \
-v /data/minio/data:/data \
-v /data/minio/config:/root/.minio \
-e "MINIO_ROOT_USER=admin" \
-e "MINIO_ROOT_PASSWORD=admin" \
minio/minio:RELEASE.2021-10-13T00-23-17Z server /data --console-address ":9090"
参数解释**
- -d:后台运行
- –restart=always:自动重启,Docker启动时,容器会自动重启
- -p:端口映射
- –name:容器运行时的名字
- -v:磁盘挂载,将容器的文件夹映射到外部的文件夹
- -e:参数设置
- –console-address:控制台端口
MinIO新版本启动需要映射两个端口,一个是Server端口:9000,一个是控制台端口:9090
浏览器输入:IP:9090即可访问
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-E63DSbb1-1635139043640)(minio-docker/1635133917313.png)]
3. MinIO权限策略
3.1 权限策略概念
IAM最新提供的一种细粒度授权的能力,可以精确到具体服务的操作、资源以及请求条件等。基于策略的授权是一种更加灵活的授权方式,能够满足企业对权限最小化的安全管控要求。例如:针对SSO服务,管理员能够控制IAM用户仅能对某一个桶资源进行指定的管理操作。多数细粒度策略以API接口为粒度进行权限拆分。
权限策略设置的是,你的服务代码用的账号(IAM用户)是否有权限创建桶、上传和下载文件对象
MinIO的权限策略分为User、Group、IAM Polices,我理解的是对应用户、角色、权限,用户可以单独分配权限策略,也可以将用户加入到Group中,为Group分配权限策略,这样用户就拥有了相应的权限策略,这里的用户就是服务代码中配置的账号和密码。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1unlxxKZ-1635139043643)(minio-docker/1635134245226.png)]
3.2 权限策略的语法
权限策略系统默认有
-
consoleAdmin(控制台管理员,最高权限)
{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": [ "admin:*" ] }, { "Effect": "Allow", "Action": [ "s3:*" ], "Resource": [ "arn:aws:s3:::*" ] } ] }
-
diagnostics(调试权限)
{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": [ "admin:Profiling", "admin:Prometheus", "admin:ServerInfo", "admin:ServerTrace", "admin:TopLocksInfo", "admin:BandwidthMonitor", "admin:ConsoleLog", "admin:OBDInfo" ], "Resource": [ "arn:aws:s3:::*" ] } ] }
-
readonly(只读)
{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": [ "s3:GetBucketLocation", "s3:GetObject" ], "Resource": [ "arn:aws:s3:::*" ] } ] }
-
readwrite(读写)
{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": [ "s3:*" ], "Resource": [ "arn:aws:s3:::*" ] } ] }
-
writeonly(只写)
{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": [ "s3:PutObject" ], "Resource": [ "arn:aws:s3:::*" ] } ] }
自定义策略
参数 | 说明 |
---|---|
Version | 标识策略的版本号,Minio中一般为"2012-10-17" |
Statement | 策略授权语句,描述策略的详细信息,包含Effect(效果)、Action(动作)、Principal(用户)、Resource(资源)和Condition(条件)。其中Condition为可选 |
Effect | Effect(效果)作用包含两种:Allow(允许)和Deny(拒绝),系统预置策略仅包含允许的授权语句,自定义策略中可以同时包含允许和拒绝的授权语句,当策略中既有允许又有拒绝的授权语句时,遵循Deny优先的原则。 |
Action | Action(动作)对资源的具体操作权限,格式为:服务名:资源类型:操作,支持单个或多个操作权限,支持通配符号*,通配符号表示所有。例如 s3:GetObject ,表示获取对象 |
Resource | Resource(资源)策略所作用的资源,支持通配符号*,通配符号表示所有。在JSON视图中,不带Resource表示对所有资源生效。Resource支持以下字符:-_0-9a-zA-Z*./\,如果Resource中包含不支持的字符,请采用通配符号*。例如:arn:aws:s3:::my-bucketname/myobject*\,表示minio中my-bucketname/myobject目录下所有对象文件。 |
Condition | Condition(条件)您可以在创建自定义策略时,通过Condition元素来控制策略何时生效。Condition包括条件键和运算符,条件键表示策略语句的Condition元素,分为全局级条件键和服务级条件键。全局级条件键(前缀为g:)适用于所有操作,服务级条件键(前缀为服务缩写,如obs:)仅适用于对应服务的操作。运算符与条件键一起使用,构成完整的条件判断语句。 |
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"s3:GetObject",
"s3:ListAllMyBuckets",
"s3:ListBucket",
"s3:PutObject",
"s3:DeleteObject",
"s3:GetBucketLocation"
],
"Resource": [
"arn:aws:s3:::test/*"
]
}
]
}
- Allow表示允许操作
- Action表示对资源的具体操作权限,对对象操作,首先要操作存储桶,所以首先这里给了存储桶查询权限,然后给了上传和下载对象的权限
- Resource表示对test存储桶下的所有资源有限
4. MinIO桶的访问规则
访问规则不同于权限策略,权限策略作用于IAM用户,而访问规则作用于所配置的桶及桶内对象是否可以通过浏览器直接访问
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WTS385no-1635139043645)(minio-docker/1635135211197.png)]
- Prefix:指明哪个文件对象,通配符*代表桶内所有文件对象
- Access:指明文件对象的访问规则
例如:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0KZl10MT-1635139043647)(minio-docker/1635135502433.png)]
当前blockchain桶没有一个访问规则,当我在访问桶内对象时,http://192.168.0.106:9000/blockchain/13.jpg 提示禁止访问
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vB0hUGs8-1635139043649)(minio-docker/1635135536332.png)]
当我设置了文件对象的访问规则后
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WWKTPjar-1635139043650)(minio-docker/1635135589893.png)]
再访问即可
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-w70Qaocc-1635139043651)(minio-docker/1635135625420.png)]
5. 与SpringBoot集成
5.1 POM文件
<!--MinIO-->
<dependency>
<groupId>io.minio</groupId>
<artifactId>minio</artifactId>
<version>8.2.1</version>
</dependency>
<!--lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<!--文件流处理工具包-->
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.2</version>
</dependency>
5.2 配置文件
# MinIO服务
minio.endpoint=http://192.168.0.106:9000
minio.accesskey=blockchain
minio.secretKey=blockchain
minio.buckname=blockchain
5.3 属性类
package com.ncepu.blockchain.config;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
/**
* @ClassName: com.ncepu.blockchain.config.MinioProp.java
* @Description:
* @Author: LiDeKun
* @Create: 2021/10/24 14:52
*/
@Data
@ConfigurationProperties(prefix = "minio")
@Component
public class MinIOProp {
private String endpoint;
private String accesskey;
private String secretKey;
private String buckname;
}
5.4 配置类
package com.ncepu.blockchain.config;
import com.ncepu.blockchain.exception.BusinessException;
import com.ncepu.blockchain.exception.code.BaseResponseCode;
import io.minio.MinioClient;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* @ClassName: com.ncepu.blockchain.config.MinIOConfig.java
* @Description:
* @Author: LiDeKun
* @Create: 2021/10/24 14:52
*/
@Configuration
@Slf4j
public class MinIOConfig {
@Autowired
private MinIOProp minioProp;
@Bean
public MinioClient minioClient() {
try {
log.info("minioClient 开始创建");
MinioClient client = MinioClient.builder().endpoint(minioProp.getEndpoint()).credentials(minioProp.getAccesskey(), minioProp.getSecretKey())
.build();
log.info("minioClient 创建成功");
return client;
} catch (Exception e) {
log.error("连接MinIO服务器异常:", e);
throw new BusinessException(BaseResponseCode.MINIO_CREATE_CLIENT_ERROR);
}
}
}
5.5 Util类
package com.ncepu.blockchain.utils;
import com.ncepu.blockchain.config.MinIOProp;
import com.ncepu.blockchain.exception.BusinessException;
import com.ncepu.blockchain.exception.code.BaseResponseCode;
import io.minio.MinioClient;
import io.minio.Result;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import io.minio.*;
import io.minio.messages.Item;
import org.springframework.web.multipart.MultipartFile;
import javax.annotation.PostConstruct;
import java.io.*;
/**
* @ClassName: com.ncepu.blockchain.utils.MinIOUtil.java
* @Description:
* @Author: LiDeKun
* @Create: 2021/10/24 14:54
*/
@Component
@Slf4j
public class MinIOUtil {
@Autowired
private MinioClient minioClient;
@Autowired
private MinIOProp minioProp;
@PostConstruct
public void initBucket() {
createBucket(minioProp.getBuckname());
}
/**
* 创建bucket
*
* @param bucketName 桶名
*/
public void createBucket(String bucketName) {
try {
if (!minioClient.bucketExists(BucketExistsArgs.builder().bucket(bucketName).build())) {
minioClient.makeBucket(MakeBucketArgs.builder().bucket(bucketName).build());
}
} catch (Exception e) {
log.error("创建桶失败:", e);
throw new BusinessException(BaseResponseCode.BUCKET_CREATE_ERROR);
}
}
/**
* 删除桶
*
* @param bucketName 桶名字
*/
public void removeBucket(String bucketName) {
try {
boolean found = minioClient.bucketExists(BucketExistsArgs.builder().bucket(bucketName).build());
if (found) {
minioClient.removeBucket(RemoveBucketArgs.builder().bucket(bucketName).build());
}
} catch (Exception e) {
log.error("删除桶失败:", e);
throw new BusinessException(BaseResponseCode.BUCKET_REMOVE_ERROR);
}
}
/**
* 判断文件是否存在
*
* @param bucketName 桶名字
* @param objectName 对象名
* @return true:存在
*/
public boolean doesObjectExist(String bucketName, String objectName) {
boolean exist = true;
try {
minioClient.statObject(StatObjectArgs.builder().bucket(bucketName).object(objectName).build());
} catch (Exception e) {
log.error("判断文件存在异常: ", e);
exist = false;
}
return exist;
}
/**
* 获取文件对象类型
*
* @param bucketName 桶名字
* @param objectName 文件对象名
* @return
*/
public String getObjectType(String bucketName, String objectName) {
try {
StatObjectResponse statObjectResponse = minioClient.statObject(StatObjectArgs.builder().bucket(bucketName).object(objectName).build());
return statObjectResponse.contentType();
} catch (Exception e) {
log.error("获取文件类型存在异常: ", e);
throw new BusinessException(BaseResponseCode.WEB3J_CLIENT_CREATE_REEOR);
}
}
/**
* 判断文件夹是否存在
*
* @param bucketName 桶名字
* @param objectName 文件夹名称(去掉“/”)
* @return true:存在
*/
public boolean doesFolderExist(String bucketName, String objectName) {
boolean exist = false;
try {
Iterable<Result<Item>> results = minioClient.listObjects(
ListObjectsArgs.builder().bucket(bucketName).prefix(objectName).recursive(false).build());
for (Result<Item> result : results) {
Item item = result.get();
if (item.isDir() && objectName.equals(item.objectName())) {
exist = true;
}
}
} catch (Exception e) {
log.error("判断文件夹是否存在异常:", e);
exist = false;
}
return exist;
}
/**
* 通过MultipartFile,上传文件对象
*
* @param bucketName 桶名字
* @param file 上传的文件
* @param objectName 对象名
* @param contentType 上传类型
* @return
*/
public void putObject(String bucketName, MultipartFile file, String objectName, String contentType) {
try {
InputStream inputStream = file.getInputStream();
minioClient.putObject(
PutObjectArgs.builder().bucket(bucketName).object(objectName).contentType(contentType)
.stream(
inputStream, inputStream.available(), -1)
.build()
);
} catch (Exception e) {
log.error("文件上传失败:", e);
throw new BusinessException(BaseResponseCode.FILE_UPLOAD_ERROR);
}
}
/**
* 从服务器本地上传文件对象
*
* @param bucketName 桶名
* @param objectName 对象名
* @param fileName 本地文件路径
*/
public void putObjectByLocalFile(String bucketName, String objectName, String fileName) {
try {
minioClient.uploadObject(UploadObjectArgs.builder()
.bucket(bucketName).object(objectName).filename(fileName).build());
} catch (Exception e) {
log.error("文件上传失败: ", e);
throw new BusinessException(BaseResponseCode.FILE_UPLOAD_ERROR);
}
}
/**
* 通过二进制流上传文件对象
*
* @param bucketName 桶名字
* @param objectName 对象名
* @param inputStream 文件流
* @param contentType 上传类型
*/
public void putObjectByStream(String bucketName, String objectName, InputStream inputStream, String contentType) {
try {
minioClient.putObject(PutObjectArgs.builder().bucket(bucketName).object(objectName).stream(
inputStream, inputStream.available(), -1).contentType(contentType)
.build());
} catch (Exception e) {
log.error("文件上传失败: ", e);
throw new BusinessException(BaseResponseCode.FILE_UPLOAD_ERROR);
}
}
/**
* 下载文件对象
*
* @param bucketName 桶名字
* @param objectName 对象名
* @return 文件流
*/
public InputStream getObject(String bucketName, String objectName) {
try {
boolean exist = doesObjectExist(bucketName, objectName);
if (exist) {
InputStream stream = minioClient.getObject(
GetObjectArgs.builder().bucket(bucketName).object(objectName).build()
);
return stream;
}
return null;
} catch (Exception e) {
log.error("文件下载失败: ", e);
throw new BusinessException(BaseResponseCode.FILE_DOWNLOAD_ERROR);
}
}
/**
* 删除文件对象
*
* @param bucketName 桶名字
* @param objectName 对象名
*/
public void removeObject(String bucketName, String objectName) {
try {
minioClient.removeObject(
RemoveObjectArgs.builder().bucket(bucketName).object(objectName).build()
);
} catch (Exception e) {
log.error("删除文件失败:", e);
throw new BusinessException(BaseResponseCode.FILE_REMOVE_ERROR);
}
}
}
5.5 Controller
package com.ncepu.blockchain.controller;
import com.ncepu.blockchain.config.MinIOProp;
import com.ncepu.blockchain.exception.code.BaseResponseCode;
import com.ncepu.blockchain.utils.DataResult;
import com.ncepu.blockchain.utils.MinIOUtil;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.IOUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* @ClassName: com.ncepu.blockchain.controller.UploadMinIOController.java
* @Description:
* @Author: LiDeKun
* @Create: 2021/10/24 16:22
*/
@RestController
@RequestMapping("/api")
@Api(tags = "上传MinIO接口")
@Slf4j
public class UploadMinIOController {
@Autowired
private MinIOUtil minIOUtil;
@Autowired
private MinIOProp minIOProp;
@PostMapping("/upload")
@ApiOperation(value = "上传MinIO接口")
public DataResult uploadFile(@RequestParam(name = "files", required = false) MultipartFile[] files){
DataResult result = DataResult.success();
if (files == null || files.length == 0) {
result.setCode(BaseResponseCode.FILE_UPLOAD_EXIST.getCode());
result.setMsg(BaseResponseCode.FILE_UPLOAD_EXIST.getMsg());
return result;
}
List<String> orgfileNameList = new ArrayList<>(files.length);
for (MultipartFile multipartFile : files) {
String orgfileName = multipartFile.getOriginalFilename();
orgfileNameList.add(orgfileName);
try {
InputStream in = multipartFile.getInputStream();
minIOUtil.putObjectByStream(minIOProp.getBuckname(), orgfileName, in, multipartFile.getContentType());
in.close();
} catch (Exception e) {
log.error(BaseResponseCode.FILE_UPLOAD_ERROR.getMsg(), e);
result.setCode(BaseResponseCode.FILE_UPLOAD_ERROR.getCode());
result.setMsg(BaseResponseCode.FILE_UPLOAD_ERROR.getMsg());
return result;
}
}
Map<String, Object> data = new HashMap<String, Object>();
data.put("bucketName", minIOProp.getBuckname());
data.put("fileName", orgfileNameList);
result.setData(data);
return result;
}
@GetMapping("/download/{fileName}")
@ApiOperation(value = "下载MinIO接口")
public void download(HttpServletResponse response, @PathVariable("fileName") String fileName) {
InputStream in = null;
try {
String contentType = minIOUtil.getObjectType(minIOProp.getBuckname(), fileName);
response.setContentType(contentType);
response.setHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(fileName, "UTF-8"));
in = minIOUtil.getObject(minIOProp.getBuckname(), fileName);
IOUtils.copy(in, response.getOutputStream());
} catch (Exception e) {
log.error(e.getMessage());
} finally {
if (in != null) {
try {
in.close();
} catch (IOException e) {
log.error(BaseResponseCode.FILE_DOWNLOAD_ERROR.getMsg(), e);
}
}
}
}
@GetMapping("/delete/{fileName}")
@ApiOperation(value = "删除MinIO接口")
public DataResult delete(@PathVariable("fileName") String fileName) {
DataResult result = DataResult.success();
minIOUtil.removeObject(minIOProp.getBuckname(), fileName);
return result;
}
}