一、安装并运行minio
其中9001为接口端口,9002为控制台端口,注意修改账户名和密码,以及数据存储路径!
docker pull minio/minio
docker run -p 9001:9001 -p 9002:9002 --name minio ^
-e "MINIO_ACCESS_KEY=admin" ^
-e "MINIO_SECRET_KEY=password" ^
-v D:/home/minio/data:/data ^
-v D:/home/minio/config:/root/.minio ^
-d --restart=always --privileged=true ^
minio/minio server /data --console-address ":9002" -address ":9001"
二、集成到SpringBoot
在pom.xml中引入jar包
<dependency>
<groupId>io.minio</groupId>
<artifactId>minio</artifactId>
<version>8.4.3</version>
</dependency>
三、MinioUtils 工具类
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;
import com.google.common.collect.Lists;
import io.minio.BucketExistsArgs;
import io.minio.GetObjectArgs;
import io.minio.GetPresignedObjectUrlArgs;
import io.minio.ListObjectsArgs;
import io.minio.MakeBucketArgs;
import io.minio.MinioClient;
import io.minio.ObjectStat;
import io.minio.ObjectWriteResponse;
import io.minio.PutObjectArgs;
import io.minio.RemoveBucketArgs;
import io.minio.RemoveObjectArgs;
import io.minio.Result;
import io.minio.StatObjectArgs;
import io.minio.UploadObjectArgs;
import io.minio.errors.ErrorResponseException;
import io.minio.errors.InsufficientDataException;
import io.minio.errors.InternalException;
import io.minio.errors.InvalidResponseException;
import io.minio.errors.ServerException;
import io.minio.errors.XmlParserException;
import io.minio.http.Method;
import io.minio.messages.Bucket;
import io.minio.messages.Item;
@Component
public class MinioUtils
{
@Autowired
private MinioClient minio;
/**
* 检查存储桶是否存在
*
* @param bucketName 存储桶名称
* @return boolean
*/
public boolean bucketExists(String bucketName) throws Exception
{
return minio.bucketExists(BucketExistsArgs.builder().bucket(bucketName).build());
}
/**
* 创建存储桶
*
* @param bucketName 存储桶名称
* @return boolean
*/
public boolean makeBucket(String bucketName) throws Exception
{
boolean flag = bucketExists(bucketName);
if (flag)
{
return true;
}
minio.makeBucket(MakeBucketArgs.builder().bucket(bucketName).build());
return true;
}
/**
* 列出所有存储桶名称
*
* @return List<String>
*/
public List<String> listBucketNames() throws Exception
{
List<Bucket> list = listBuckets();
return list.stream().filter(Objects::nonNull).map(o -> o.name()).collect(Collectors.toList());
}
/**
* 列出所有存储桶
*
* @return List<Bucket>
*/
public List<Bucket> listBuckets() throws Exception
{
return minio.listBuckets();
}
/**
* 删除存储桶
*
* @param bucketName 存储桶名称
* @return boolean
*/
public boolean removeBucket(String bucketName) throws Exception
{
Iterable<Result<Item>> myObjects = listObjects(bucketName);
for (Result<Item> result : myObjects)
{
Item item = result.get();
// 有对象文件,则删除失败
if (item.size() > 0)
{
return false;
}
}
// 删除存储桶,注意,只有存储桶为空时才能删除成功。
minio.removeBucket(RemoveBucketArgs.builder().bucket(bucketName).build());
return !bucketExists(bucketName);
}
/**
* 在桶里创建文件夹对象
*
* @param bucketName 存储桶名称
* @param fileName 对象名称
* @return void
*/
public void createFile(String bucketName, String fileName) throws Exception
{
try
{
if (!bucketExists(bucketName))
{
// minio服务器创建桶
makeBucket(bucketName);
}
minio.putObject(
PutObjectArgs.builder().bucket(bucketName).object(fileName).stream(
new ByteArrayInputStream(new byte[]
{}), 0, -1)
.build());
}
catch (ErrorResponseException e)
{
e.printStackTrace();
}
catch (InsufficientDataException e)
{
e.printStackTrace();
}
catch (InternalException e)
{
e.printStackTrace();
}
catch (InvalidKeyException e)
{
e.printStackTrace();
}
catch (InvalidResponseException e)
{
e.printStackTrace();
}
catch (IOException e)
{
e.printStackTrace();
}
catch (NoSuchAlgorithmException e)
{
e.printStackTrace();
}
catch (ServerException e)
{
e.printStackTrace();
}
catch (XmlParserException e)
{
e.printStackTrace();
}
}
/**
* 列出存储桶中的所有对象
*
* @param bucketName 存储桶名称
* @return Iterable<Result<Item>>
*/
public Iterable<Result<Item>> listObjects(String bucketName) throws Exception
{
return minio.listObjects(ListObjectsArgs.builder().bucket(bucketName).build());
}
/**
* 列出存储桶中的所有对象名称
*
* @param bucketName 存储桶名称
* @return List<String>
*/
public List<String> listObjectNames(String bucketName) throws Exception
{
List<String> ret = Lists.newArrayList();
Iterable<Result<Item>> myObjects = listObjects(bucketName);
for (Result<Item> result : myObjects)
{
Item item = result.get();
ret.add(item.objectName());
}
return ret;
}
/**
* 上传本地文件
*
* @param bucketName 存储桶名称
* @param objectName 对象名称
* @param fileName 本地文件路径
*
* @return ObjectWriteResponse
*/
public ObjectWriteResponse uploadObject(String bucketName, String objectName, String fileName) throws Exception
{
UploadObjectArgs args = UploadObjectArgs.builder()
.bucket(bucketName)
.object(objectName)
.filename(fileName)
.build();
return minio.uploadObject(args);
}
/**
* 上传MultipartFile
*
* @param bucketName 存储桶名称
* @param objectName 对象名称
* @param multipartFile 文件
*
* @return ObjectWriteResponse
*/
public ObjectWriteResponse putObject(String bucketName, String objectName, MultipartFile multipartFile) throws Exception
{
InputStream inputStream = multipartFile.getInputStream();
PutObjectArgs args = PutObjectArgs.builder()
.bucket(bucketName)
.object(objectName)
.contentType(multipartFile.getContentType())
.stream(inputStream, multipartFile.getSize(), -1)
.build();
ObjectWriteResponse owr = minio.putObject(args);
inputStream.close();
return owr;
}
/**
* 通过InputStream上传对象
*
* @param bucketName 存储桶
* @param objectName 对象名称
* @param in 要上传的流
* @param contentType 要上传的文件类型 MimeTypeUtils.IMAGE_JPEG_VALUE
*
* @return ObjectWriteResponse
*/
public ObjectWriteResponse putObject(String bucketName, String objectName, InputStream in, String contentType) throws Exception
{
PutObjectArgs args = PutObjectArgs.builder()
.bucket(bucketName)
.object(objectName)
.contentType(contentType)
.stream(in, in.available(), -1)
.build();
return minio.putObject(args);
}
/**
* 以流的形式获取一个文件对象
*
* @param bucketName 存储桶
* @param objectName 对象名称
*
* @return InputStream
*/
public InputStream getObject(String bucketName, String objectName) throws Exception
{
boolean flag = bucketExists(bucketName);
if (!flag)
{
return null;
}
ObjectStat resp = statObject(bucketName, objectName);
return resp == null
? null
: minio.getObject(GetObjectArgs.builder().bucket(bucketName).object(objectName).build());
}
/**
* 获取对象的元数据
*
* @param bucketName 存储桶
* @param objectName 对象名称
*
* @return StatObjectResponse
*/
public ObjectStat statObject(String bucketName, String objectName) throws Exception
{
return !bucketExists(bucketName)
? null
: minio.statObject(StatObjectArgs.builder().bucket(bucketName).object(objectName).build());
}
/**
* 删除文件
*
* @param fileName
*
* @return void
*/
public void removeMinio(String fileName) throws Exception
{
try
{
minio.removeObject(RemoveObjectArgs.builder().bucket("test").object(fileName).build());
}
catch (Exception e)
{
e.printStackTrace();
}
}
/**
* 获取文件外链
*
* @param bucketName 存储桶
* @param objectName 对象名称
*
* @return String
*/
public String getObjectURL(String bucketName, String objectName) throws Exception
{
GetPresignedObjectUrlArgs build = GetPresignedObjectUrlArgs.builder()
.method(Method.GET)
.bucket(bucketName)
.object(objectName)
// .expiry(60 * 60 * 24)
.build();
return minio.getPresignedObjectUrl(build);
}
}
四、使用方法
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Objects;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.io.IOUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.multipart.MultipartFile;
import com.cxxx.common.exception.file.FileNameLengthLimitExceededException;
import com.cxxx.common.utils.DateUtils;
import com.cxxx.common.utils.file.FileTypeUtils;
import com.cxxx.common.utils.file.FileUploadUtils;
import com.cxxx.common.utils.file.FileUtils;
import com.cxxx.common.utils.file.MimeTypeUtils;
import com.cxxx.common.utils.file.MinioUtils;
import com.cxxx.common.utils.uuid.IdUtils;
import com.cxxx.framework.config.MinioConfig;
import com.cxxx.framework.web.domain.AjaxResult;
import com.cxxx.project.system.attachment.domain.Attachment;
import com.cxxx.project.system.attachment.service.IAttachmentService;
import io.minio.ObjectWriteResponse;
/**
* 通用请求处理
*
* @author system
*/
@Controller
@RequestMapping("/attachment")
public class AttachmentController
{
private static final Logger log = LoggerFactory.getLogger(AttachmentController.class);
@Autowired
private MinioUtils minioUtils;
@Autowired
private MinioConfig minioConfig;
@Autowired
private IAttachmentService attachmentService;
@PostMapping("/upload")
@ResponseBody
public AjaxResult upload(MultipartFile file, String moduleName, Long moduleId) throws Exception
{
AjaxResult ajax = AjaxResult.success();
if (file == null)
{
return AjaxResult.error("未选择文件!");
}
try
{
// 文件名长度检验
int fileNameLength = Objects.requireNonNull(file.getOriginalFilename()).length();
if (fileNameLength > FileUploadUtils.DEFAULT_FILE_NAME_LENGTH)
{
throw new FileNameLengthLimitExceededException(FileUploadUtils.DEFAULT_FILE_NAME_LENGTH);
}
// 文件校验(大小和类型)
FileUploadUtils.assertAllowed(file, MimeTypeUtils.DEFAULT_ALLOWED_EXTENSION);
String bucketName = minioConfig.getBucketName(); // 桶名称
String dirPath = DateUtils.datePath(); // 文件夹名称
// 上传文件
String originalFilename = file.getOriginalFilename();
String extName = FileTypeUtils.getFileType(originalFilename);
String minioFileName = dirPath + "/" + IdUtils.fastSimpleUUID() + "." + extName;
ObjectWriteResponse owr = minioUtils.putObject(bucketName, minioFileName, file);
// 入库
Attachment attachment = attachmentService.insertAttachmentByFile(file, moduleName, moduleId, owr.object());
ajax.put("file", attachment);
}
catch (Exception e)
{
log.error("上传文件出错:{}", e);
return AjaxResult.error(e.getMessage());
}
return ajax;
}
@GetMapping("/download")
public void download(String fileId, HttpServletResponse response) throws Exception
{
Attachment sysAttachment = attachmentService.selectAttachmentByFileId(Long.parseLong(fileId));
if (sysAttachment == null)
{
throw new Exception("文件记录不存在");
}
String bucketName = minioConfig.getBucketName();
InputStream is = minioUtils.getObject(bucketName, sysAttachment.getFilePath());
response.setContentType(MediaType.APPLICATION_OCTET_STREAM_VALUE);
FileUtils.setAttachmentResponseHeader(response, sysAttachment.getFileName());
OutputStream os = response.getOutputStream();
try
{
byte[] b = new byte[1024];
int length;
while ((length = is.read(b)) > 0)
{
os.write(b, 0, length);
}
}
catch (IOException e)
{
throw e;
}
finally
{
IOUtils.close(os);
IOUtils.close(is);
}
}
}