一、服务器安装minio
1.进行下载
下载地址:
https://dl.min.io/server/minio/release/linux-amd64/minio
2.新建minio安装目录,执行如下命令
mkdir -p /home/minio/data
把二进制文件上传到安装目录后,执行:
chmod +x minio //给予权限//注意:以前的老版本minio的配置中,配置用户名和密码时,是这两个参数:
MINIO_ACCESS_KEY 和MINIO_SECRET_KEY
而现在比较新的版本的minio,需要替换成MINIO_ROOT_USER和MINIO_ROOT_PASSWORD
export MINIO_ROOT_USER=fsp-manage
export MINIO_ROOT_PASSWORD=springboot-fsp-manage
./minio server /home/minio/data //启动
后台启动,并打印日志
自定义端口方式:自定义启动端口号以及控制台端口号,不设置则控制台会自动配置其他端口号,非常不方便
nohup ./minio server --address :9000 --console-address :9001 /home/minio/data > /home/minio/data/minio.log &
ps -ef|grep minio
配置开机自启
[Unit]
Description=minio
After=network.target
[Service]
Type=simple
Environment=MINIO_ROOT_USER=minio
Environment=MINIO_ROOT_PASSWORD=minio123456
ExecStart=/usr/local/Mes/env/BINARY/minio server --address :9000 --console-address :9001 /usr/local/Mes/env/minio/data
ExecReload=/bin/kill -HUP $MAINPID
KillMode=process
Restart=always
LimitNOFILE=65536
[Install]
WantedBy=multi-user.target
二、进行访问,并设置桶
地址:http://127.0.0.1:9000
三、springboot进行实现
1.引入依赖
<!--minio客户端工具-->
<dependency>
<groupId>io.minio</groupId>
<artifactId>minio</artifactId>
<version>8.0.0</version>
</dependency>
<!-- alibaba的fastjson -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.51</version>
</dependency>
<!-- thymeleaf模板引擎 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
2.在 application.yml 文件中加入 MinIO 服务器的相关信息
minio:
endpoint: http://serverip
port: 9002
accessKey: fsp-manage
secretKey: springboot-fsp-manage
bucketName: fsp-dev
secure: false
3.创建实体类
这一步,我们将配置文件中 minio 的配置信息通过注解的方式注入到 MinioProp 这个实体中,方便后面我们使用
package com.xiaomifeng1010.minio.configuration;
import io.minio.MinioClient;
import io.minio.errors.InvalidPortException;
import lombok.Getter;
import lombok.Setter;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Component;
/**
* @author xiaomifeng1010
* @version 1.0
* @date: 2022/5/20 10:30
* @Description minio配置
*/
@Configuration
@Component
@ConfigurationProperties(prefix = "minio")
@Getter
@Setter
public class MinioConfig {
private String endpoint;
private int port;
private String accessKey;
private String secretKey;
private Boolean secure;
private String bucketName;
@Bean
public MinioClient getMinioClient() throws InvalidPortException {
MinioClient minioClient = MinioClient.builder().endpoint(endpoint, port, secure)
.credentials(accessKey, secretKey)
.build();
return minioClient;
}
//
// @Bean(name = "multipartResolver")
// public MultipartResolver multipartResolver(){
// CommonsMultipartResolver resolver = new CommonsMultipartResolver();
// resolver.setDefaultEncoding("UTF-8");
// //resolveLazily属性启用是为了推迟文件解析,以在在UploadAction中捕获文件大小异常
// resolver.setResolveLazily(true);
// resolver.setMaxInMemorySize(40960);
// //上传文件大小 50M 50*1024*1024
// resolver.setMaxUploadSize(50*1024*1024);
// return resolver;
// }
}
如何你要配置ip和port在同一个参数中,不分开,或者是直接配置域名(域名映射了ip和port),那么配置的yml 修改如下:
把port注释掉,同时配置类也修改一下就可以了:
5、上传工具类
package com.hanclouds.wx.mes.basic.common.utils;
import io.minio.*;
import io.minio.errors.*;
import io.minio.http.Method;
import io.minio.messages.Bucket;
import io.minio.messages.DeleteObject;
import io.minio.messages.Item;
import lombok.SneakyThrows;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
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.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
@Component
public class MinioUtils {
@Autowired
private MinioClient minioClient;
/**
* 判断桶是否存在
*/
@SneakyThrows(Exception.class)
public boolean bucketExists(String bucketName) {
return minioClient.bucketExists(BucketExistsArgs.builder().bucket(bucketName).build());
}
/**
* 创建桶
*
* @param bucketName 获取全部的桶 minioClient.listBuckets();
*/
@SneakyThrows(Exception.class)
public void createBucket(String bucketName) {
if (!minioClient.bucketExists(BucketExistsArgs.builder().bucket(bucketName).build())) {
minioClient.makeBucket(MakeBucketArgs.builder().bucket(bucketName).build());
}
}
/**
* 根据bucketName获取信息
*
* @param bucketName bucket名称
*/
@SneakyThrows(Exception.class)
public Optional<Bucket> getBucket(String bucketName) {
return minioClient.listBuckets().stream().filter(b -> b.name().equals(bucketName)).findFirst();
}
/**
* 根据bucketName删除信息
*
* @param bucketName bucket名称
*/
@SneakyThrows(Exception.class)
public void removeBucket(String bucketName) {
minioClient.removeBucket(RemoveBucketArgs.builder().bucket(bucketName).build());
}
/**
* 移除指定桶下边的指定文件
*
* @param bucketName
* @param objectName
*/
public void removeFile(String bucketName, String objectName) throws ServerException, InsufficientDataException, ErrorResponseException, IOException, NoSuchAlgorithmException, InvalidKeyException, InvalidResponseException, XmlParserException, InternalException {
List<DeleteObject> deleteObjects =new ArrayList<>();
deleteObjects.add(new DeleteObject(objectName));
minioClient.removeObject(RemoveObjectArgs.builder()
.bucket(bucketName)
.object(objectName)
.build());
}
/**
* 获取文件流
*
* @param bucketName bucket名称
* @param objectName 文件名称
* @return 二进制流
*/
@SneakyThrows(Exception.class)
public InputStream getObject(String bucketName, String objectName) {
return minioClient.getObject(GetObjectArgs.builder().bucket(bucketName).object(objectName).build());
}
/**
* 上传本地文件
*
* @param bucketName 存储桶
* @param objectName 对象名称
* @param fileName 本地文件路径
*/
public ObjectWriteResponse putObject(String bucketName, String objectName, String fileName) {
ObjectWriteResponse o = null;
try {
o = minioClient.uploadObject(UploadObjectArgs.builder().bucket(bucketName).object(objectName).filename(fileName).build());
} catch (Exception e) {
e.printStackTrace();
}
return o;
}
/**
* 通过流上传文件
*
* @param bucketName 存储桶
* @param objectName 文件对象
* @param inputStream 文件流
*/
@SneakyThrows(Exception.class)
public ObjectWriteResponse putObject(String bucketName, String objectName, InputStream inputStream) {
if (!bucketExists(bucketName)) {
createBucket(bucketName);
}
return minioClient.putObject(PutObjectArgs.builder().bucket(bucketName).object(objectName).stream(inputStream, inputStream.available(), -1).build());
}
/**
* 單個文件上傳遞
*
* @param bucketName
* @param multipartFile
* @return
*/
@SneakyThrows(Exception.class)
public String uploadFileSingle(String bucketName, String fileName, MultipartFile multipartFile) {
if (!bucketExists(bucketName)) {
createBucket(bucketName);
}
InputStream in = null;
try {
in = multipartFile.getInputStream();
minioClient.putObject(PutObjectArgs.builder().bucket(bucketName).object(fileName).stream(in, in.available(), -1).contentType("application/octet-stream").build());
} catch (Exception e) {
e.printStackTrace();
} finally {
if (in != null) {
try {
in.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return getUploadObjectUrl(bucketName, fileName, 7 * 24 * 60 * 60);
}
/**
* description: 上传文件
*
* @param multipartFile
* @return: java.lang.String
*/
public List<String> uploadFileBatch(String bucketName, MultipartFile[] multipartFile) {
if (!bucketExists(bucketName)) {
createBucket(bucketName);
}
List<String> names = new ArrayList<>();
for (MultipartFile file : multipartFile) {
try {
String fileName = file.getOriginalFilename();
uploadFileSingle(bucketName, fileName, file);
names.add(fileName);
} catch (Exception e) {
e.printStackTrace();
}
}
return names;
}
/**
* 获取文件外链
*
* @param bucketName bucket名称
* @param objectName 文件名称
* @param expires 过期时间 <=7 秒级
* @return url
*/
@SneakyThrows(Exception.class)
public String getUploadObjectUrl(String bucketName, String objectName, Integer expires) {
String presignedObjectUrl = minioClient.getPresignedObjectUrl(GetPresignedObjectUrlArgs.builder().method(Method.PUT).bucket(bucketName).object(objectName).expiry(expires).build());
//分割问号前边的部分获取到预览地址
String[] split = presignedObjectUrl.split("\\?");
String s1 = split[0];
return s1;
}
/**
* 下载文件
* bucketName:桶名
*
* @param fileName: 文件名
*/
@SneakyThrows(Exception.class)
public void download(String bucketName, String fileName, HttpServletResponse response) {
// 获取对象的元数据
StatObjectResponse stat = minioClient.statObject(StatObjectArgs.builder().bucket(bucketName).object(fileName).build());
response.setContentType(stat.contentType());
response.setCharacterEncoding("UTF-8");
response.setHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(fileName, "UTF-8"));
InputStream is = minioClient.getObject(GetObjectArgs.builder().bucket(bucketName).object(fileName).build());
IOUtils.copy(is, response.getOutputStream());
is.close();
}
/**
* 将minio中的文件保存到本地
*
* @param bucketName 桶名称
* @param fileName 文件名称
* @param filePath 文件保存路径
* @throws IOException
* @throws NoSuchAlgorithmException
* @throws InvalidKeyException
*/
@SneakyThrows(MinioException.class)
public void trasitionFileTo(String bucketName, String fileName, String filePath) throws IOException, NoSuchAlgorithmException, InvalidKeyException {
minioClient.downloadObject(DownloadObjectArgs.builder()
.bucket(bucketName)
.object(fileName)
.filename(filePath)
.build());
}
/**
* 将一个桶下边的文件复制到该桶的一个文件夹下
*
* @param bucketName
* @param fileName
* @param destinationObjectName
*/
@SneakyThrows(Exception.class)
public void copyObject(String bucketName, String fileName, String destinationObjectName) {
minioClient.copyObject(
CopyObjectArgs.builder()
.source(CopySource.builder()
.bucket(bucketName)
.object(fileName).build())
.bucket(bucketName)
.object(destinationObjectName)
.build()
);
}
}
6.controller接口
package com.dcboot.module.minio.controller;
import cn.hutool.core.io.FileUtil;
import com.dcboot.base.util.ApiResult;
import com.dcboot.module.minio.configuration.MinioConfig;
import com.dcboot.module.minio.dto.response.MinioResponseDTO;
import com.dcboot.module.minio.entity.MinioFile;
import com.dcboot.module.util.MinioClientUtils;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiOperation;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.RandomStringUtils;
import org.apache.commons.lang3.math.NumberUtils;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import java.io.FileInputStream;
import java.time.Instant;
import java.util.ArrayList;
import java.util.List;
/**
* @author xiaomifeng1010
* @version 1.0
* @date: 2022/5/21 10:33
* @Description minio 文件处理(上传,下载,获取文件地址等)
*/
@Controller
@RequestMapping("/fileHandle")
@Slf4j
@AllArgsConstructor
@Api(tags = "文件处理模块")
public class FileHandleController {
private MinioClientUtils minioClientUtils;
private MinioConfig minioConfig;
@PostMapping(value = {"/admin/uploadFile","/web/uploadFile"})
@ResponseBody
@ApiOperation(value = "上传文件,支持批量上传")
@ApiImplicitParam(name = "files",value = "文件对象",dataType = "File")
public ApiResult uploadFile(@RequestParam("files") List<MultipartFile> files) {
log.info(files.toString());
if (CollectionUtils.isEmpty(files)){
return ApiResult.error("未选择文件!");
}
List<MinioResponseDTO> MinioResponseDTOList=new ArrayList<>();
for (MultipartFile file : files) {
String originalFilename = file.getOriginalFilename();
// 获取文件拓展名
String extName = FileUtil.extName(originalFilename);
log.info("文件拓展名:"+extName);
// 生成新的文件名,存入到minio
long millSeconds = Instant.now().toEpochMilli();
String minioFileName=millSeconds+ RandomStringUtils.randomNumeric(12)+"."+extName;
String contentType = file.getContentType();
log.info("文件mime:{}",contentType);
// 返回文件大小,单位字节
long size = file.getSize();
log.info("文件大小:"+size);
try {
String bucketName = minioConfig.getBucketName();
minioClientUtils.putObject(bucketName,file,minioFileName);
String fileUrl = minioClientUtils.getObjectUrl(bucketName, minioFileName);
MinioFile minioFile = new MinioFile();
minioFile.setOriginalFileName(originalFilename);
minioFile.setFileExtName(extName);
minioFile.setFileName(minioFileName);
minioFile.setFileSize(size);
minioFile.setMime(contentType);
minioFile.setIsDelete(NumberUtils.INTEGER_ZERO);
minioFile.setFileUrl(fileUrl);
boolean insert = minioFile.insert();
if (insert) {
MinioResponseDTO minioResponseDTO = new MinioResponseDTO();
minioResponseDTO.setFileId(minioFile.getId());
minioResponseDTO.setOriginalFileName(originalFilename);
minioResponseDTO.setFileUrl(fileUrl);
MinioResponseDTOList.add(minioResponseDTO);
}
} catch (Exception e) {
log.error("上传文件出错:{}",e);
return ApiResult.error("上传文件出错");
}
}
return ApiResult.success(MinioResponseDTOList);
}
/**
* 仅仅用于测试,是否可以正常上传文件
* @return
* @throws Exception
*/
@GetMapping("/test")
@ApiOperation(value = "测试minio文件上传")
public ApiResult testPutObject() throws Exception {
FileInputStream fileInputStream = new FileInputStream("C:\\Users\\MSI\\Desktop\\新建文本文档.txt");
boolean bs = minioClientUtils.putObject("fsp-dev", "新建文本文档.txt", fileInputStream, "image/jpg");
log.info("上传成功?"+bs);
return ApiResult.success("上传成功");
}
}
删除文件:
//文件删除
@DeleteMapping
public String delete(String name) {
try {
MinioClient minioClient = new MinioClient(ENDPOINT, ACCESSKEY, SECRETKEY);
minioClient.removeObject(BUCKETNAME, name);
} catch (Exception e) {
return "删除失败"+e.getMessage();
}
return "删除成功";
}
7.如何获取多级目录文件,例如在桶下有一个file文件夹,文件夹下边有一个aa.txt
String bucketName = "log";
String objectName = "file/aa.txt";
InputStream stream = minioClient.getObject(bucketName,objectName);