minIO介绍
MinIO 是在 GNU Affero 通用公共许可证 v3.0 下发布的高性能对象存储。它与 Amazon S3 云存储服务 API 兼容。使用 MinIO 为机器学习、分析和应用程序数据工作负载构建高性能基础架构。
高性能
MinIO 是全球领先的对象存储先锋,目前在全世界有数百万的用户. 在标准硬件上,读/写速度上高达183 GB / 秒 和 171 GB / 秒。对象存储可以充当主存储层,以处理Spark、Presto、TensorFlow、H2O.ai等各种复杂工作负载以及成为Hadoop HDFS的替代品。MinIO用作云原生应用程序的主要存储,与传统对象存储相比,云原生应用程序需要更高的吞吐量和更低的延迟。而这些都是MinIO能够达成的性能指标。
可扩展性
MinIO利用了Web缩放器的来之不易的知识,为对象存储带来了简单的缩放模型。 这是我们坚定的理念 “简单可扩展.” 在 MinIO, 扩展从单个群集开始,该群集可以与其他MinIO群集联合以创建全局名称空间, 并在需要时可以跨越多个不同的数据中心。 通过添加更多集群可以扩展名称空间, 更多机架,直到实现目标。
云的原生支持
MinIO 是在过去4年的时间内从0开始打造的一款软件 ,符合一切原生云计算的架构和构建过程,并且包含最新的云计算的全新的技术和概念。 其中包括支持Kubernetes 、微服和多租户的的容器技术。使对象存储对于 Kubernetes更加友好。
简单
极简主义是MinIO的指导性设计原则。简单性减少了出错的机会,提高了正常运行时间,提供了可靠性,同时简单性又是性能的基础。 只需下载一个二进制文件然后执行,即可在几分钟内安装和配置MinIO。 配置选项和变体的数量保持在最低限度,这样让失败的配置概率降低到接近于0的水平。 MinIO升级是通过一个简单命令完成的,这个命令可以无中断的完成MinIO的升级,并且不需要停机即可完成升级操作 - 降低总使用和运维成本。
minIO安装
使用Docker 安装 Standalone MinIO
生产环境请使用分布式安装 运行docker命令
docker run \
-p 9000:9000 \
-p 9001:9001 \
-e "MINIO_ROOT_USER=AKIAIOSFODNN7EXAMPLE" \
-e "MINIO_ROOT_PASSWORD=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY" \
quay.io/minio/minio server /data --console-address ":9001"
MINIO_ROOT_USER为登录账号,MINIO_ROOT_PASSWORD为登录密码
开放9000和9001端口
控制台
spring cloud 集成
添加依赖
implementation 'io.minio:minio'
FileServiceImpl.java
package com.f.service.impl;
import com.f.config.FileProperties;
import com.f.constant.Constant;
import com.f.dto.file.GetObjectDto;
import com.f.dto.file.PreUrlGetObjectDto;
import com.f.dto.file.PreUrlPutObjectDto;
import com.f.dto.file.PutObjectDto;
import com.f.service.FileService;
import com.f.utils.FileUtils;
import com.f.utils.IdUtils;
import io.minio.GetObjectArgs;
import io.minio.GetObjectResponse;
import io.minio.GetPresignedObjectUrlArgs;
import io.minio.MinioClient;
import io.minio.ObjectWriteResponse;
import io.minio.PutObjectArgs;
import io.minio.http.Method;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import okhttp3.HttpUrl;
import org.apache.commons.lang3.StringUtils;
import org.jetbrains.annotations.NotNull;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import javax.annotation.PostConstruct;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
/**
* 文件服务实现
*
* @author liuf
* @date 2022/2/22 14:18
*/
@Slf4j
@RequiredArgsConstructor
@Service
public class FileServiceImpl implements FileService {
private final FileProperties fileProperties;
private MinioClient minioClient;
@PostConstruct
private synchronized void init() {
if (minioClient == null) {
minioClient = MinioClient.builder()
.endpoint(Objects.requireNonNull(HttpUrl.parse(fileProperties.getMinIoUrl())))
.credentials(fileProperties.getAccessKey(), fileProperties.getAccessSecret())
.region(fileProperties.getRegion())
.build();
log.info("init minioClient");
}
}
@SneakyThrows
@Override
public String putObject(PutObjectDto putObjectDto) {
final ObjectWriteResponse response = minioClient.putObject(PutObjectArgs.builder().bucket(putObjectDto.getBucket())
.object(getName(putObjectDto.getSuffix()))
.stream(putObjectDto.getStream(), putObjectDto.getObjectSize(), putObjectDto.getPartSize())
.build());
log.info("result:{}", response);
return response.object();
}
@SneakyThrows
@Override
public String putMultipartFile(PutObjectDto putObjectDto, MultipartFile file) {
putObjectDto.setStream(file.getInputStream());
putObjectDto.setSuffix(Constant.DOT + StringUtils.substringAfterLast(Constant.DOT, file.getOriginalFilename()));
return putObject(putObjectDto);
}
@NotNull
private String getName(String suffix) {
return IdUtils.uuid() + suffix;
}
@SneakyThrows
@Override
public String getPreSignedPutObjectUrl(PreUrlPutObjectDto preUrlObjectDto) {
return minioClient.getPresignedObjectUrl(GetPresignedObjectUrlArgs.builder()
.bucket(preUrlObjectDto.getBucket())
.object(getName(preUrlObjectDto.getSuffix()))
.expiry(FileUtils.getExpiry(preUrlObjectDto.getExpiry()))
.method(Method.PUT)
.build());
}
@SneakyThrows
@Override
public String getPreSignedGetObjectUrl(PreUrlGetObjectDto preUrlGetObjectDto) {
return minioClient.getPresignedObjectUrl(GetPresignedObjectUrlArgs.builder()
.bucket(preUrlGetObjectDto.getBucket())
.object(preUrlGetObjectDto.getName())
.expiry(FileUtils.getExpiry(preUrlGetObjectDto.getExpiry()))
.method(Method.GET)
.build());
}
@Override
public List<String> getPreSignedPutObjectUrlList(PreUrlPutObjectDto preUrlObjectDto) {
final int size = preUrlObjectDto.getSize();
List<String> list = new ArrayList<>(size);
for (int i = 0; i < size; i++) {
list.add(getPreSignedPutObjectUrl(preUrlObjectDto));
}
return list;
}
@SneakyThrows
@Override
public InputStream getObject(GetObjectDto getObjectDto) {
final GetObjectResponse response = minioClient.getObject(GetObjectArgs.builder()
.bucket(getObjectDto.getBucket())
.object(getObjectDto.getName())
.build());
log.info("result:{}", response);
return response;
}
}
FileController.java
package com.f.controller;
import com.f.base.Result;
import com.f.dto.file.GetObjectDto;
import com.f.dto.file.PreUrlGetObjectDto;
import com.f.dto.file.PreUrlPutObjectDto;
import com.f.dto.file.PutObjectDto;
import com.f.service.FileService;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import java.io.InputStream;
import java.util.List;
/**
* 文件服务
*
* @author liuf
* @date 2022/2/22 14:18
*/
@RequiredArgsConstructor
@RestController
public class FileController {
private final FileService fileService;
/**
* 上传
*
* @param putObjectDto 上传对象
* @return 路径
* @date 2022年2月23日
*/
@PutMapping("/putMultipartFile")
public Result<String> putMultipartFile(PutObjectDto putObjectDto, @RequestParam("file") MultipartFile file) {
return Result.success(fileService.putMultipartFile(putObjectDto, file));
}
/**
* 上传
*
* @param putObjectDto 上传对象
* @return 路径
* @date 2022年2月23日
*/
@PutMapping("/putObject")
public Result<String> putObject(PutObjectDto putObjectDto) {
return Result.success(fileService.putObject(putObjectDto));
}
/**
* 预上传url生成
*
* @param preUrlObjectDto dto
* @return url
*/
@GetMapping("/getPreSignedPutObjectUrl")
public Result<String> getPreSignedPutObjectUrl(PreUrlPutObjectDto preUrlObjectDto) {
return Result.success(fileService.getPreSignedPutObjectUrl(preUrlObjectDto));
}
/**
* 预下载url生成
*
* @param preUrlGetObjectDto dto
* @return url
*/
@GetMapping("/getPreSignedGetObjectUrl")
public Result<String> getPreSignedGetObjectUrl(PreUrlGetObjectDto preUrlGetObjectDto) {
return Result.success(fileService.getPreSignedGetObjectUrl(preUrlGetObjectDto));
}
/**
* 预上传url生成 批量
*
* @param preUrlObjectDto dto
* @return url
*/
@GetMapping("/getPreSignedPutObjectUrlList")
public Result<List<String>> getPreSignedPutObjectUrlList(PreUrlPutObjectDto preUrlObjectDto) {
return Result.success(fileService.getPreSignedPutObjectUrlList(preUrlObjectDto));
}
/**
* 下载
*
* @param getObjectDto 对象
* @return 文件流
* @date 2022年2月23日
*/
@GetMapping("/getObject")
public InputStream getObject(@ModelAttribute GetObjectDto getObjectDto) {
return fileService.getObject(getObjectDto);
}
}
FileApplication.java
package com.f;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
/**
* 文件服务启动 @EnableDiscoveryClient 默认会启用
*
* @author liuf
* @date 2022/2/22 15:25
*/
@SpringBootApplication(scanBasePackages = "com.f", exclude = {DataSourceAutoConfiguration.class})
public class FileApplication {
public static void main(String[] args) {
SpringApplication.run(FileApplication.class, args);
}
}
前端集成
api/index.ts
import axios from 'axios';
import { FILE_API, FILE_BUCKET, FILE_EXPIRY } from '/@/api/constant';
import request from '/@/utils/request';
// 配置新建一个 axios 实例
const fileRequest = axios.create({
timeout: 15000,
headers: { 'Content-Type': 'application/octet-stream' },
});
export interface MyFile {
file: File, // 文件
}
/**
* 预上传请求路径
* @param suffix 文件后缀
* @returns 返回预请求路径
*/
export function getPreSignedPutObjectUrl(suffix: string) {
return request.get(FILE_API + '/getPreSignedPutObjectUrl', {
params: {
"expiry": FILE_EXPIRY,
"suffix": suffix,
"bucket": FILE_BUCKET
}
});
}
/**
* 上传文件接口
* @param file 文件
* @param url 上传路径
* @returns 路径
*/
export function uploadFileByUrl(file: File, url: string) {
return fileRequest.put(url, file)
}
view/personal/idnex.vue
<template>
<div class="personal">
<el-row>
<!-- 个人信息 -->
<el-col :xs="24" :sm="16">
<el-card shadow="hover" header="个人信息">
<div class="personal-user">
<div class="personal-user-left">
<el-upload class="h100 personal-user-left-upload" :http-request="upload" multiple :limit="1">
<img :src="getSrc(getUserInfos.headImage)" />
</el-upload>
</div>
<div class="personal-user-right">
<el-row>
<el-col :span="24" class="personal-title mb18">{{ currentTime }},admin,生活变的再糟糕,也不妨碍我变得更好!</el-col>
<el-col :span="24">
<el-row>
<el-col :xs="24" :sm="8" class="personal-item mb6">
<div class="personal-item-label">昵称:</div>
<div class="personal-item-value">{{ getUserInfos.name }}</div>
</el-col>
<el-col :xs="24" :sm="16" class="personal-item mb6">
<div class="personal-item-label">身份:</div>
<div class="personal-item-value">{{ getUserInfos.name }}</div>
</el-col>
</el-row>
</el-col>
<el-col :span="24">
<el-row>
<el-col :xs="24" :sm="8" class="personal-item mb6">
<div class="personal-item-label">登录IP:</div>
<div class="personal-item-value">192.168.1.1</div>
</el-col>
<el-col :xs="24" :sm="16" class="personal-item mb6">
<div class="personal-item-label">登录时间:</div>
<div class="personal-item-value">{{ getUserInfos.createTime }}</div>
</el-col>
</el-row>
</el-col>
</el-row>
</div>
</div>
</el-card>
</el-col>
</el-row>
</div>
</template>
<script lang="ts">
import { toRefs, reactive, computed } from 'vue';
import { formatAxis } from '/@/utils/formatTime';
import { newsInfoList, recommendList } from './mock';
import { useStore } from '/@/store/index';
import { getPreSignedPutObjectUrl, MyFile, uploadFileByUrl } from '/@/api/file';
import { update } from '/@/api/system/user';
import { getPath, getSuffix, getSrc } from '/@/utils/common';
import { SET_USER_INFOS, USER_INFOS_MODULE } from '/@/api/constant';
import { encrypt } from '/@/utils/aes';
export default {
name: 'personal',
setup() {
const store = useStore();
const state = reactive({
newsInfoList,
recommendList,
personalForm: {
name: '',
email: '',
autograph: '',
occupation: '',
phone: '',
sex: '',
},
});
// 当前时间提示语
const currentTime = computed(() => {
return formatAxis(new Date());
});
// 获取用户信息 vuex
const getUserInfos = computed(() => {
return store.state.userInfos.userInfos;
});
const upload = (file: MyFile) => {
getPreSignedPutObjectUrl(getSuffix(file.file.name)).then(res => {
uploadFileByUrl(file.file, res.data).then(() => {
// 修改用户头像
encrypt(JSON.stringify({ "id": (getUserInfos.value as any).id, "headImage": getPath(res.data) }))
.then(data => {
update({ data })
})
store.dispatch(USER_INFOS_MODULE + '/' + SET_USER_INFOS, { ...getUserInfos.value, "headImage": getPath(res.data) });
})
})
}
return {
currentTime,
getUserInfos,
upload,
getSrc,
...toRefs(state),
};
},
};
</script>