文件服务算是一个基础服务了,比较专业的组件有FastDFS,也可以采用基本的保存在本地的文件上传方式,但是其安全性不高。对于中小公司来说,可以直接购买云存储服务,当然也可以自己搭建,下面就聊聊springboot集成文件服务组件MinIO,看看这个组件带来了那些惊喜。
MinIO是一个基于Apache License v2.0开源协议的对象存储服务,非常合适存储大容量非结构化的数据,如图片、视频、日志文件等,一个对象文件的大小可以是几KB到5T不等。MinIO是一个轻量级的服务,类似redis、mysql,可以很方便的和其他应用结合,并且自带非常方便的可视化界面,让人心生喜欢。
MinIO的特点请查看官网,https://min.io/。
1、MinIO安装
进入官网下载对应的版本,可以选择linux、windows、docker等,本次选择windows环境
windows环境minio安装启动
查看minio版本
minio.exe -version
首先进入minio.exe所在目录,在cmd命令行中输入如下命令即完成安装启动,其中server后面的地址是文件上传的存储目录。
minio.exe server D:\minio
api端口是9000,所以下面的yml文件中端口需要配置为9000,accessKey和secretKey需要配置为启动窗口上对应的值。
启动成功后,访问默认地址http://127.0.0.1:9000/minio进入到minio控制台,输入账号密码登录即可。
可以在控制台创建bucketName,也可以在代码中创建。注意在启动服务后使用minio。
linux环境minio安装启动
创建data目录
mkdir/usr/local/minio/data
添加可执行权限
chmod +x minio
设置登录的access_key
export MINIO_ACCESS_KEY=minioadmin
设置登录的secret key
export MINIO_SECRET_KEY=minioadmin
启动 minio
./minio server /usr/local/minio/data
或指定端口号
./minio server /usr/local/minio/data --address=0.0.0.0:9001
其他同windows环境。
2、springboot集成MinIO
创建springboot工程,引入web依赖、minio依赖
<dependency>
<groupId>io.minio</groupId>
<artifactId>minio</artifactId>
<version>8.2.1</version>
</dependency>
配置application.yml文件
spring:
servlet:
multipart:
max-file-size: 200MB
max-request-size: 200MB
minio:
endpoint: http://localhost:9000
accessKey: minioadmin
secretKey: minioadmin
nginxUrl: http://localhost:9000
创建Properties类接收上面四个属性
@ConfigurationProperties(prefix = "minio")
public class MinioProperties {
/**
* 连接地址
*/
private String endpoint;
/**
* 用户名
*/
private String accessKey;
/**
* 密码
*/
private String secretKey;
/**
* 域名
*/
private String nginxUrl;
public String getEndpoint() {
return endpoint;
}
public void setEndpoint(String endpoint) {
this.endpoint = endpoint;
}
public String getAccessKey() {
return accessKey;
}
public void setAccessKey(String accessKey) {
this.accessKey = accessKey;
}
public String getSecretKey() {
return secretKey;
}
public void setSecretKey(String secretKey) {
this.secretKey = secretKey;
}
public String getNginxUrl() {
return nginxUrl;
}
public void setNginxUrl(String nginxUrl) {
this.nginxUrl = nginxUrl;
}
}
创建配置类,注入MinIOClient
@Configuration
@EnableConfigurationProperties(MinioProperties.class)
public class MinioConfig {
@Autowired
private MinioProperties minioProperties;
/**
* 获取MinioClient
*/
@Bean
public MinioClient minioClient() {
return MinioClient
.builder()
.endpoint(minioProperties.getEndpoint())
.credentials(minioProperties.getAccessKey(), minioProperties.getSecretKey())
.build();
}
}
创建Client工具类,方便操作文件
@Component
public class MinioUtil {
@Autowired
private MinioProperties minioProperties;
@Autowired
private MinioClient minioClient;
/**
* 创建bucket
*/
public void createBucket(String bucketName) throws Exception {
if (!minioClient.bucketExists(BucketExistsArgs.builder().bucket(bucketName).build())) {
minioClient.makeBucket(MakeBucketArgs.builder().bucket(bucketName).build());
}
}
/**
* 删除bucket
*/
public void removeBucket(String bucketName) throws Exception {
minioClient.removeBucket(RemoveBucketArgs.builder().bucket(bucketName).build());
}
/**
* 获取全部bucket
*/
public List<Bucket> getBuckets() throws Exception {
return minioClient.listBuckets();
}
/**
* 根据bucketName获取bucket
* @param bucketName bucket名称
*/
public Optional<Bucket> getBucket(String bucketName) throws IOException, InvalidKeyException, NoSuchAlgorithmException, InsufficientDataException, InvalidResponseException, InternalException, ErrorResponseException, ServerException, XmlParserException, ServerException {
return minioClient.listBuckets().stream().filter(b -> b.name().equals(bucketName)).findFirst();
}
/**
* 上传文件
*/
public MinioObject uploadFile(MultipartFile file, String bucketName) throws Exception {
//判断文件是否为空,为空则返回
if (null == file || 0 == file.getSize()) {
return null;
}
//判断存储桶是否存在,不存在则创建
createBucket(bucketName);
//文件名
String originalFilename = file.getOriginalFilename();
//新的文件名 = 存储桶文件名_时间戳.后缀名
assert originalFilename != null;
String fileName = bucketName + "_" +
new SimpleDateFormat("yyyy-MM-dd").format(new Date()) + "_" +
new Random().nextInt(1000) +
originalFilename.substring(originalFilename.lastIndexOf("."));
//开始上传
minioClient.putObject(
PutObjectArgs
.builder()
.bucket(bucketName)
.object(fileName)
.stream(file.getInputStream(), file.getSize(), -1)
.contentType(file.getContentType())
.build());
String url = minioProperties.getEndpoint() + "/" + bucketName + "/" + fileName;
String urlHost = minioProperties.getNginxUrl() + "/" + bucketName + "/" + fileName;
return new MinioObject(url, urlHost);
}
/**
* 下载文件
*/
public void downloadFile(String filename, String bucketName, HttpServletResponse httpServletResponse){
GetObjectArgs objectArgs = GetObjectArgs.builder().bucket(bucketName).object(filename).build();
try (GetObjectResponse response = minioClient.getObject(objectArgs)){
byte[] buf = new byte[1024];
int len;
try (FastByteArrayOutputStream os = new FastByteArrayOutputStream()){
while ((len=response.read(buf))!=-1){
os.write(buf,0,len);
}
os.flush();
byte[] bytes = os.toByteArray();
httpServletResponse.setCharacterEncoding("utf-8");
// 设置强制下载不打开
httpServletResponse.setContentType("application/force-download");
httpServletResponse.addHeader("Content-Disposition", "attachment;fileName=" + filename);
try ( ServletOutputStream stream = httpServletResponse.getOutputStream()){
stream.write(bytes);
stream.flush();
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 下载文件
*/
public void downloadObject(String fileName, String bucketName, HttpServletResponse httpServletResponse) {
try {
GetObjectArgs objectArgs = GetObjectArgs.builder().bucket(bucketName).object(fileName).build();
InputStream file = minioClient.getObject(objectArgs);
String filename = new String(fileName.getBytes("ISO8859-1"), "utf-8");
httpServletResponse.setHeader("Content-Disposition", "attachment;filename=" + filename);
ServletOutputStream servletOutputStream = httpServletResponse.getOutputStream();
int len;
byte[] buffer = new byte[1024];
while ((len = file.read(buffer)) > 0) {
servletOutputStream.write(buffer, 0, len);
}
servletOutputStream.flush();
file.close();
servletOutputStream.close();
} catch (ErrorResponseException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 上传⽂件
*
* @param bucketName bucket名称
* @param objectName ⽂件名称
* @param stream ⽂件流
* @throws Exception https://docs.minio.io/cn/java-client-api-reference.html#putObject
*/
public void putObject(String bucketName, String objectName, InputStream stream) throws Exception {
minioClient.putObject(PutObjectArgs.builder().bucket(bucketName).object(objectName).stream(stream, stream.available(), -1).contentType(objectName.substring(objectName.lastIndexOf("."))).build());
}
/**
* 上传⽂件
*
* @param bucketName bucket名称
* @param objectName ⽂件名称
* @param stream ⽂件流
* @param size ⼤⼩
* @param contextType 类型
* @throws Exception https://docs.minio.io/cn/java-client-api-reference.html#putObject
*/
public void putObject(String bucketName, String objectName, InputStream stream, long size, String contextType) throws Exception {
minioClient.putObject(PutObjectArgs.builder().bucket(bucketName).object(objectName).stream(stream, size, -1).contentType(contextType).build());
}
/**
* 删除⽂件
*
* @param bucketName bucket名称
* @param objectName ⽂件名称
* @throws Exception https://docs.minio.io/cn/java-client-apireference.html#removeObject
*/
public void removeObject(String bucketName, String objectName) throws Exception {
minioClient.removeObject(RemoveObjectArgs.builder().bucket(bucketName).object(objectName).build());
}
/**
* 获取⽂件url
*
* @param bucketName bucket名称
* @param objectName ⽂件名称
* @param expires 过期时间 <=7
* @return url
*/
public String getObjectURL(String bucketName, String objectName, Integer expires) throws Exception {
return minioClient.getPresignedObjectUrl(GetPresignedObjectUrlArgs.builder().method(Method.GET).bucket(bucketName).object(objectName).expiry(expires).build());
}
/**
* 获取⽂件输入流
*
* @param bucketName bucket名称
* @param objectName ⽂件名称
* @return ⼆进制流
*/
public InputStream getObject(String bucketName, String objectName) throws Exception {
return minioClient.getObject(GetObjectArgs.builder().bucket(bucketName).object(objectName).build());
}
/**
* 获取⽂件信息
*
* @param bucketName bucket名称
* @param objectName ⽂件名称
* @throws Exception https://docs.minio.io/cn/java-client-api-reference.html#statObject
*/
public StatObjectResponse getObjectInfo(String bucketName, String objectName) throws Exception {
return minioClient.statObject(StatObjectArgs.builder().bucket(bucketName).object(objectName).build());
}
/**
* 查看文件对象列表
*
* @param bucketName 存储bucket名称
* @return 存储bucket内文件对象信息
*/
public List<MinioObject> listObjects(String bucketName) {
Iterable<Result<Item>> results = minioClient.listObjects(ListObjectsArgs.builder().bucket(bucketName).build());
List<MinioObject> objectItems = new ArrayList<>();
try {
for (Result<Item> result : results) {
Item item = result.get();
MinioObject minioObject = new MinioObject();
minioObject.setMinIoUrl(item.objectName());;
minioObject.setSize(item.size());
objectItems.add(minioObject);
}
} catch (Exception e) {
e.printStackTrace();
return null;
}
return objectItems;
}
}
访问接口
@PostMapping("/upload")
public String fileUpload(MultipartFile file) throws Exception {
MinioObject minioObject = minioUtil.uploadFile(file, "public");
System.out.println("bucket01.getMinIoUrl() = " + minioObject.getMinIoUrl());
System.out.println("bucket01.getNginxUrl() = " + minioObject.getNginxUrl());
return minioObject.getMinIoUrl();
}
@GetMapping("download")
public Object download(HttpServletResponse response) throws Exception {
minioUtil.downloadObject("","image",response);
return "success";
}
文件上传完成之后如图
注意:可以通过可视化界面设置存储桶的权限级别,是否开发,访问的url有效期等,具体看组件提供的方法,如
getObjectURL()获取桶内文件的url地址,如果设置了只写权限,用户直接访问地址是查看不了的;
getObgect()可以获取桶内对应文件的流对象,进行文件流传输;
presignedGetObject()返回的是进行加密算法的地址,通过它可以直接访问文件;
getPresignedObjectUrl(Method.GET,"asiatrip","minio.png",10,null)有效期10秒,可以通过get方式访问;
makeBucket 创建桶
listBuckets 列出当前所有桶
bucketExists 判断桶是否存在
removeBucket 移除桶
listObjects 获取桶内的内容
listIncompleteUploads 获取上传中断的内容,支持断点续传
getObject 下载文件
putObject 上传文件
文件存储可以使用之后也可以通过配置nginx的方式对静态资源进行代理,前端直接通过url进行访问。