微服务整合minio分布式存储(docker部署使用minio)
前言: MinIO 是一个基于Apache License v2.0开源协议的对象存储服务。它兼容亚马逊S3云存储服务接口,非常适合于存储大容量非结构化的数据,如照片,视频,日志文件,备份和容器/ VM映像。对象的大小可以从几KB到最大5TB。
MinIO是一个非常轻量的服务,可以很简单的和其他应用的结合,类似 NodeJS, Redis 或者 MySQL。
一. docker部署minio
- 拉取最新minio镜像
docker pull minio/minio:latest
- Docker 启动Minio镜像(推荐:官方文档)
docker run \
-p 9000:9000 \
-p 9001:9001 \
--name minio1 \
-v /mnt/data:/data \
-e "MINIO_ROOT_USER=minioadmin" \
-e "MINIO_ROOT_PASSWORD=minioadmin" \
quay.io/minio/minio server /data --console-address ":9001"
- 开放端口浏览器进行访问:http://ip:9000 (账号密码:minioadmin)
二.SpringBoot整合minio进行文件上传
1. 导入最新依赖
<!-- 分布式对象存储 -->
<dependency>
<groupId>io.minio</groupId>
<artifactId>minio</artifactId>
<version>7.1.0</version>
</dependency>
2. 项目中minio配置
minio.endpoint=http://ip:9000
minio.port=9000
minio.access-key=minioadmin
minio.secret-key=minioadmin
minio.secure=false
minio.bucket-name=commons
minio.image-size=10485760
minio.file-size=1073741824
minio.url=https://域名/
3. 实现代码如下:
- 引用minio配置内容
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
@Data
@Configuration
@ConfigurationProperties(prefix = "minio")
public class MinIOProperties {
private String endpoint;
private Integer port;
private String accessKey;
private String secretKey;
private String url;
/**
* //"如果是true,则用的是https而不是http,默认值是true"
*/
private Boolean secure;
/**
* //"默认存储桶"
*/
private String bucketName;
/**
* 图片的最大大小
*/
private long imageSize;
/**
* 其他文件的最大大小
*/
private long fileSize;
}
- minio service层方法实现 (注意设置桶的policy策略,不然会出问题,如下),文件路径是进行拼接的
import cn.hutool.core.lang.Assert;
import io.minio.*;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;
@Slf4j
@Component
@EnableConfigurationProperties({MinIOProperties.class})
public class MinIOService implements InitializingBean {
private MinIOProperties minIOProperties;
public MinIOService(MinIOProperties minIOProperties){
this.minIOProperties=minIOProperties;
}
private MinioClient client;
@Override
public void afterPropertiesSet() {
Assert.notBlank(minIOProperties.getEndpoint(), "MinIO URL 为空");
Assert.notBlank(minIOProperties.getAccessKey(), "MinIO accessKey为空");
Assert.notBlank(minIOProperties.getSecretKey(), "MinIO secretKey为空");
this.client = new MinioClient.Builder()
.credentials(minIOProperties.getAccessKey(), minIOProperties.getSecretKey())
.endpoint(minIOProperties.getEndpoint(),minIOProperties.getPort(),minIOProperties.getSecure())
.build();
}
/**
* 检查存储桶是否存在
*
* @param bucketName 存储桶名称
* @return
*/
@SneakyThrows
public boolean bucketExists(String bucketName) {
boolean found = client.bucketExists(BucketExistsArgs.builder().bucket(bucketName).build());
if (found) {
System.out.println(bucketName + " exists");
} else {
System.out.println(bucketName + " does not exist");
}
return found;
}
@SneakyThrows
public void createBucketIfAbsent(String bucketName) {
System.out.println(bucketExists(bucketName));
if (!bucketExists(bucketName)) {
MakeBucketArgs makeBucketArgs = MakeBucketArgs.builder()
.bucket(bucketName).build();
client.makeBucket(makeBucketArgs);
//设置桶的policy策略
client.setBucketPolicy(SetBucketPolicyArgs.builder().bucket(bucketName).config(MinioPolicyConstant.READ_WRITE.replace(MinioPolicyConstant.BUCKET_PARAM,
bucketName)).build());
System.out.println("创建成功");
}
}
public String putObject(String bucketName, String objectName, MultipartFile file) throws Exception {
createBucketIfAbsent(bucketName);
PutObjectArgs putObjectArgs = PutObjectArgs.builder()
.bucket(bucketName)
.object(objectName)
// .contentType(MediaType.ALL_VALUE)
.contentType(file.getContentType())
.stream(file.getInputStream(), file.getInputStream().available(), -1)
.build();
client.putObject(putObjectArgs);
// String path = client.getObjectUrl(bucketName, objectName);
// String path = GlobalConstants.FILR_HEAD_UTL +bucketName+"/"+objectName;
String path = minIOProperties.getUrl() +bucketName+"/"+objectName;
return path;
}
public void removeObject(String bucketName, String objectName) throws Exception {
RemoveObjectArgs removeObjectArgs = RemoveObjectArgs.builder()
.bucket(bucketName)
.object(objectName)
.build();
client.removeObject(removeObjectArgs);
}
}
- 配置桶的policy策略
public class MinioPolicyConstant {
public static final String BUCKET_PARAM = "${bucket}";
/**
* bucket权限-只读
*/
public static final String READ_ONLY = "{\"Version\":\"2012-10-17\",\"Statement\":[{\"Effect\":\"Allow\",\"Principal\":{\"AWS\":[\"*\"]},\"Action\":[\"s3:GetBucketLocation\",\"s3:ListBucket\"],\"Resource\":[\"arn:aws:s3:::" + BUCKET_PARAM + "\"]},{\"Effect\":\"Allow\",\"Principal\":{\"AWS\":[\"*\"]},\"Action\":[\"s3:GetObject\"],\"Resource\":[\"arn:aws:s3:::" + BUCKET_PARAM + "/*\"]}]}";
/**
* bucket权限-只读
*/
public static final String WRITE_ONLY = "{\"Version\":\"2012-10-17\",\"Statement\":[{\"Effect\":\"Allow\",\"Principal\":{\"AWS\":[\"*\"]},\"Action\":[\"s3:GetBucketLocation\",\"s3:ListBucketMultipartUploads\"],\"Resource\":[\"arn:aws:s3:::" + BUCKET_PARAM + "\"]},{\"Effect\":\"Allow\",\"Principal\":{\"AWS\":[\"*\"]},\"Action\":[\"s3:AbortMultipartUpload\",\"s3:DeleteObject\",\"s3:ListMultipartUploadParts\",\"s3:PutObject\"],\"Resource\":[\"arn:aws:s3:::" + BUCKET_PARAM + "/*\"]}]}";
/**
* bucket权限-读写
*/
public static final String READ_WRITE = "{\"Version\":\"2012-10-17\",\"Statement\":[{\"Effect\":\"Allow\",\"Principal\":{\"AWS\":[\"*\"]},\"Action\":[\"s3:GetBucketLocation\",\"s3:ListBucket\",\"s3:ListBucketMultipartUploads\"],\"Resource\":[\"arn:aws:s3:::" + BUCKET_PARAM + "\"]},{\"Effect\":\"Allow\",\"Principal\":{\"AWS\":[\"*\"]},\"Action\":[\"s3:DeleteObject\",\"s3:GetObject\",\"s3:ListMultipartUploadParts\",\"s3:PutObject\",\"s3:AbortMultipartUpload\"],\"Resource\":[\"arn:aws:s3:::" + BUCKET_PARAM + "/*\"]}]}";
}
- 最后控制层接口引用及接口测试
import cn.hutool.core.util.IdUtil;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiImplicitParams;
import io.swagger.annotations.ApiOperation;
import lombok.AllArgsConstructor;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
@Api(tags = "文件接口")
@RestController
@RequestMapping("/v1/files")
@Slf4j
@AllArgsConstructor
public class MinIOController {
private MinIOService minIOService;
@Log(title = "文件上传", businessType = BusinessType.UPFILE)
@PostMapping
@ApiOperation(value = "文件上传", httpMethod = "POST")
@ApiImplicitParams({
@ApiImplicitParam(name = "file", value = "文件", paramType = "form", dataType = "__file"),
@ApiImplicitParam(name = "bucketName", value = "桶名称", paramType = "query", dataType = "string")
})
public Result<String> upload(
@RequestParam(value = "file") MultipartFile file,
@RequestParam(value = "bucketName", required = false, defaultValue = "default") String bucketName
) {
try {
String fileType = FileTypeUtils.getFileType(file);
if (fileType != null) {
String suffix = file.getOriginalFilename().substring(file.getOriginalFilename().lastIndexOf(".") + 1);
String objectName = IdUtil.simpleUUID() + "." + suffix;
String path = minIOService.putObject(bucketName, objectName, file);
return Result.success(path);
}
return Result.failed("不支持的文件格式。请确认格式,重新上传!!!");
} catch (Exception e) {
throw new BizException(e.getMessage());
}
}
@Log(title = "文件删除", businessType = BusinessType.DELETE)
@DeleteMapping
@ApiOperation(value = "文件删除", httpMethod = "DELETE")
@ApiImplicitParams({
@ApiImplicitParam(name = "path", value = "文件路径", required = true, paramType = "query"),
})
@SneakyThrows
public Result removeFile(@RequestParam String path) {
int lastIndex = path.lastIndexOf("/");
String bucketName = path.substring(path.lastIndexOf("/", lastIndex - 1) + 1, lastIndex);
String objectName = path.substring(lastIndex + 1);
minIOService.removeObject(bucketName, objectName);
return Result.success();
}
}
- 测试
完结:之前做文件存储实现的一个minio案例,有其他问题再进行改进,特意再此记录一下
感谢!