Minio文件存储
About 3 min
Minio文件存储
微服务环境下,我们要尽量将服务进行拆分,让不同的服务只做好自己的事情,例如我们的设计中,包含文件服务,用户服务,聊天服务的等。
这样的好处是:如果有一天我们发现文件的上传下载占用的带宽特别高,那么肯定需要扩容。
如果将文件服务耦合在用户服务或者聊天服务中,那么就相当于要对整个用户服务进行扩容,得不偿失。
而我们将文件服务分开,就可以更好的对文件服务单一的进行扩容操作,更好的利用资源。
Docker运行Minio
- 拉取镜像
docker pull minio/minio
- 运行容器
docker run -p 9000:9000 -p 9090:9090 \
--name minio -d --restart=always \
-e "MINIO_ACCESS_KEY=minio" \
-e "MINIO_SECRET_KEY=minio123" \
-v ~/tools/docker-volumes/minio:/data \
-v ~/tools/docker-volumes/minio-config:/root/.minio \
minio/minio server /data --console-address ":9090"
新建一个桶
设置为公开的桶
新建一个密钥:
Minio配置
依赖
<dependency>
<groupId>io.minio</groupId>
<artifactId>minio</artifactId>
<version>8.4.5</version>
</dependency>
导入oss配置properties
oss.enabled=true
oss.type=minio
oss.endpoint=http://localhost:9000
oss.access-key=lIJJ2kaz6F4fmCu1IDiu
oss.secret-key=z7tD0vNXmB8LYZkfPag9aQ9QNxVeaQXrDli7Eejm
oss.bucketName=yunfei-chat
OSS属性配置
@Data
@ConfigurationProperties(prefix = "oss")
public class OssProperties {
/**
* 是否开启
*/
Boolean enabled;
/**
* 存储对象服务器类型
*/
OssType type;
/**
* OSS 访问端点,集群时需提供统一入口
*/
String endpoint;
/**
* 用户名
*/
String accessKey;
/**
* 密码
*/
String secretKey;
/**
* 存储桶
*/
String bucketName;
}
Minio功能
@Slf4j
@AllArgsConstructor
public class MinIOTemplate {
/**
* MinIO 客户端
*/
MinioClient minioClient;
/**
* MinIO 配置类
*/
OssProperties ossProperties;
/**
* 查询所有存储桶
*
* @return Bucket 集合
*/
@SneakyThrows
public List<Bucket> listBuckets() {
return minioClient.listBuckets();
}
/**
* 桶是否存在
*
* @param bucketName 桶名
* @return 是否存在
*/
@SneakyThrows
public boolean bucketExists(String bucketName) {
return minioClient.bucketExists(BucketExistsArgs.builder().bucket(bucketName).build());
}
/**
* 创建存储桶
*
* @param bucketName 桶名
*/
@SneakyThrows
public void makeBucket(String bucketName) {
if (!bucketExists(bucketName)) {
minioClient.makeBucket(MakeBucketArgs.builder().bucket(bucketName).build());
}
}
/**
* 删除一个空桶 如果存储桶存在对象不为空时,删除会报错。
*
* @param bucketName 桶名
*/
@SneakyThrows
public void removeBucket(String bucketName) {
minioClient.removeBucket(RemoveBucketArgs.builder().bucket(bucketName).build());
}
/**
* 返回临时带签名、过期时间一天、PUT请求方式的访问URL
*/
@SneakyThrows
public OssResp getPreSignedObjectUrl(OssReq req) {
String absolutePath = req.isAutoPath() ? generateAutoPath(req) : req.getFilePath() + StrUtil.SLASH + req.getFileName();
String url = minioClient.getPresignedObjectUrl(
GetPresignedObjectUrlArgs.builder()
.method(Method.PUT)
.bucket(ossProperties.getBucketName())
.object(absolutePath)
.expiry(60 * 60 * 24)
.build());
return OssResp.builder()
.uploadUrl(url)
.downloadUrl(getDownloadUrl(ossProperties.getBucketName(), absolutePath))
.build();
}
private String getDownloadUrl(String bucket, String pathFile) {
return ossProperties.getEndpoint() + StrUtil.SLASH + bucket + pathFile;
}
/**
* GetObject接口用于获取某个文件(Object)。此操作需要对此Object具有读权限。
*
* @param bucketName 桶名
* @param ossFilePath Oss文件路径
*/
@SneakyThrows
public InputStream getObject(String bucketName, String ossFilePath) {
return minioClient.getObject(
GetObjectArgs.builder().bucket(bucketName).object(ossFilePath).build());
}
/**
* 查询桶的对象信息
*
* @param bucketName 桶名
* @param recursive 是否递归查询
* @return
*/
@SneakyThrows
public Iterable<Result<Item>> listObjects(String bucketName, boolean recursive) {
return minioClient.listObjects(
ListObjectsArgs.builder().bucket(bucketName).recursive(recursive).build());
}
/**
* 生成随机文件名,防止重复
*
* @return
*/
public String generateAutoPath(OssReq req) {
String uid = Optional.ofNullable(req.getUid()).map(String::valueOf).orElse("000000");
cn.hutool.core.lang.UUID uuid = cn.hutool.core.lang.UUID.fastUUID();
String suffix = FileNameUtil.getSuffix(req.getFileName());
String yearAndMonth = DateUtil.format(new Date(), DatePattern.NORM_MONTH_PATTERN);
return req.getFilePath() + StrUtil.SLASH + yearAndMonth + StrUtil.SLASH + uid + StrUtil.SLASH + uuid + StrUtil.DOT + suffix;
}
/**
* 获取带签名的临时上传元数据对象,前端可获取后,直接上传到Minio
*
* @param bucketName
* @param fileName
* @return
*/
@SneakyThrows
public Map<String, String> getPreSignedPostFormData(String bucketName, String fileName) {
// 为存储桶创建一个上传策略,过期时间为7天
PostPolicy policy = new PostPolicy(bucketName, ZonedDateTime.now().plusDays(7));
// 设置一个参数key,值为上传对象的名称
policy.addEqualsCondition("key", fileName);
// 添加Content-Type以"image/"开头,表示只能上传照片
policy.addStartsWithCondition("Content-Type", "image/");
// 设置上传文件的大小 64kiB to 10MiB.
policy.addContentLengthRangeCondition(64 * 1024, 10 * 1024 * 1024);
return minioClient.getPresignedPostFormData(policy);
}
}
封装为starter
@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties(OssProperties.class)
@ConditionalOnExpression("${oss.enabled}")
@ConditionalOnProperty(value = "oss.type", havingValue = "minio")
public class MinIOConfiguration {
@Bean
@SneakyThrows
@ConditionalOnMissingBean(MinioClient.class)
public MinioClient minioClient(OssProperties ossProperties) {
return MinioClient.builder()
.endpoint(ossProperties.getEndpoint())
.credentials(ossProperties.getAccessKey(), ossProperties.getSecretKey())
.build();
}
@Bean
@ConditionalOnBean({MinioClient.class})
@ConditionalOnMissingBean(MinIOTemplate.class)
public MinIOTemplate minioTemplate(MinioClient minioClient, OssProperties ossProperties) {
return new MinIOTemplate(minioClient, ossProperties);
}
}
在resources目录下的META-INF
的spring.factories
中
# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.yunfei.chat.oss.minio.MinIOConfiguration
##测试
@Test
public void getUploadUrl() {
OssReq ossReq = OssReq.builder()
.fileName("test.jpeg")
.filePath("/test")
.autoPath(false)
.build();
OssResp preSignedObjectUrl = minIOTemplate.getPreSignedObjectUrl(ossReq);
System.out.println(preSignedObjectUrl);
}
拿uploadUrl进行上传,这里选body里面的二进制文件
上传成功