MinIO是一个开源的对象存储服务器,它与Amazon S3兼容,并且专注于高性能和可扩展性。在本教程中,我们将学习如何使用MinIO搭建私有对象存储服务,以便在本地或私有云环境中存储和管理数据。
首先,我们需要安装MinIO服务器。MinIO提供了预编译的二进制文件,适用于各种操作系统。您可以从官方网站下载适用于您的操作系统的二进制文件。
在安装完MinIO二进制文件后,我们需要启动MinIO服务器。您可以通过以下命令在默认端口(9000)上启动MinIO:
$ ./minio server /path/to/data/directory
其中,/path/to/data/directory
是您希望MinIO用于存储数据的目录路径。执行上述命令后,MinIO服务器将在本地启动。
启动MinIO服务器后,您可以通过浏览器访问MinIO的管理界面。默认情况下,管理界面位于http://localhost:9000
。
http://localhost:9000
并按下Enter键。在MinIO的登录页面,您可以使用默认的访客访问密钥来登录:
minio
minio123
登录后,您将可以看到MinIO的仪表板,其中包含有关服务器和存储桶的信息。
在MinIO中,您需要创建一个存储桶来存储对象(文件)。存储桶类似于文件系统中的文件夹,您可以使用它来组织和管理数据。
现在,您可以开始上传和下载对象到MinIO服务器中的存储桶中。
docker run -d -p 9000:9000 -p 9001:9001 --name=minio --restart=always --privileged=true -e "MINIO_ROOT_USER='账号'" -e "MINIO_ROOT_PASSWORD='密码'" -v /usr/local/dev/dockerdata/minio/data:/upload -v /usr/local/dev/dockerdata/minio/config:/root/.minio minio/minio server /upload --console-address ":9001" --address ":9000"
命令详解:
docker run: 运行一个新的容器。
-d: 在后台运行容器。
-p 9000:9000 -p 9001:9001: 将容器的端口9000和9001映射到主机的9000和9001端口,这样可以通过主机的这两个端口来访问MinIO服务和MinIO Web界面。
--name=minio: 为容器指定一个名称为"minio",方便后续操作时使用。
--restart=always: 设置容器在启动后总是自动重启,以确保MinIO服务始终可用。
--privileged=true: 启用特权模式,允许容器内部的进程拥有访问主机内核的权限。
-e "MINIO_ROOT_USER=用户名" -e "MINIO_ROOT_PASSWORD=密码": 设置MinIO的根用户的用户名和密码。在这里,用户名为"用户名",密码为"密码"。
-v /usr/local/dev/dockerdata/minio/data:/data -v /usr/local/dev/dockerdata/minio/config:/root/.minio: 将主机上的两个目录挂载到容器内部。/usr/local/dev/dockerdata/minio/data用于存储MinIO的数据,/usr/local/dev/dockerdata/minio/config用于存储MinIO的配置信息。
minio/minio: 指定要使用的MinIO镜像。
server /data: 指定MinIO的数据存储路径为/data。
--console-address ":9001" --address ":9000": 设置MinIO的控制台地址为":9001",即可以通过主机的9001端口访问MinIO的Web控制台;同时设置MinIO的服务地址为":9000",即MinIO服务将在主机的9000端口监听。
<dependency>
<groupId>io.miniogroupId>
<artifactId>minioartifactId>
<version>7.1.0version>
dependency>
<dependency>
<groupId>cn.hutoolgroupId>
<artifactId>hutool-allartifactId>
<version>${hutool.version}version>
dependency>
spring:
name: minio
servlet:
multipart:
max-file-size: 10MB
max-request-size: 10MB
#minio配置
minio:
access-key: 账号
secret-key: 密码
url: http://你的IP:9000/
bucket-name: 桶名称
import io.minio.MinioClient;
import lombok.Data;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
@Data
public class MinioConfig {
@Value("${spring.minio.access-key}")
private String accessKey;
@Value("${spring.minio.secret-key}")
private String secretKey;
@Value("${spring.minio.url}")
private String url;
@Value("${spring.minio.bucket-name}")
private String bucketName;
@Bean
public MinioClient minioClient() {
return MinioClient.builder()
.endpoint(url)
.credentials(accessKey, secretKey)
.build();
}
}
import cn.hutool.core.io.FastByteArrayOutputStream;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import io.minio.*;
import io.minio.http.Method;
import io.minio.messages.Bucket;
import io.minio.messages.Item;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.List;
@Component
@Slf4j
@RequiredArgsConstructor
public class MinioUtil {
//必须使用注入的方式否则会出现空指针
@Autowired
MinioClient minioClient;
@Value("${spring.minio.bucket-name}")
private String bucketName;
/**
* 查看存储bucket是否存在
* bucketName 需要传入桶名
*
* @return boolean
*/
public Boolean bucketExists(String bucketName) {
Boolean found;
try {
found = minioClient.bucketExists(BucketExistsArgs.builder().bucket(bucketName).build());
} catch (Exception e) {
e.printStackTrace();
return false;
}
return found;
}
/**
* 创建存储bucket
* bucketName 需要传入桶名
*
* @return Boolean
*/
public Boolean makeBucket(String bucketName) {
try {
minioClient.makeBucket(MakeBucketArgs.builder()
.bucket(bucketName)
.build());
} catch (Exception e) {
e.printStackTrace();
return false;
}
return true;
}
/**
* 删除存储bucket
* bucketName 需要传入桶名
*
* @return Boolean
*/
public Boolean removeBucket(String bucketName) {
try {
minioClient.removeBucket(RemoveBucketArgs.builder()
.bucket(bucketName)
.build());
} catch (Exception e) {
e.printStackTrace();
return false;
}
return true;
}
/**
* 获取全部bucket
*/
public List getAllBuckets() {
try {
List buckets = minioClient.listBuckets();
return buckets;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
/**
* 文件上传
*
* @param file 文件
* BucketName 需要传入桶名
* @return Boolean
*/
public String upload(MultipartFile file) {
String originalFilename = file.getOriginalFilename();
if (StringUtils.isBlank(originalFilename)) {
throw new RuntimeException();
}
String objectName = String.valueOf(System.currentTimeMillis()) + "【tswl】" + originalFilename;
try {
PutObjectArgs objectArgs = PutObjectArgs.builder().bucket(bucketName).object(objectName)
.stream(file.getInputStream(), file.getSize(), -1).contentType(file.getContentType()).build();
//文件名称相同会覆盖
minioClient.putObject(objectArgs);
} catch (Exception e) {
e.printStackTrace();
return null;
}
return objectName;
}
/**
* 预览
*
* @param fileName BucketName 需要传入桶名
* @return
*/
public String preview(String fileName) {
// 查看文件地址
GetPresignedObjectUrlArgs build = new GetPresignedObjectUrlArgs().builder().bucket("BucketName").object(fileName).method(Method.GET).build();
try {
String url = minioClient.getPresignedObjectUrl(build);
return url;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
/**
* 文件下载
*
* bucketName 桶名
*
* @param fileName 文件名称(包含扩展名)
* @param res response
*/
public void download(String fileName, HttpServletResponse res) {
GetObjectArgs objectArgs = GetObjectArgs.builder().bucket(bucketName).object(fileName).build();
try {
InputStream is = minioClient.getObject(objectArgs);
byte[] buf = new byte[1024];
int len;
try (FastByteArrayOutputStream os = new FastByteArrayOutputStream()) {
while ((len = is.read(buf)) != -1) {
os.write(buf, 0, len);
}
os.flush();
byte[] bytes = os.toByteArray();
res.setCharacterEncoding("UTF-8");
res.setContentType("application/octet-stream"); // 设置内容类型为二进制流
res.addHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(fileName, "UTF-8"));
try (ServletOutputStream stream = res.getOutputStream()) {
stream.write(bytes);
stream.flush();
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 查看文件对象
* BucketName 需要传入桶名
*
* @return 存储bucket内文件对象信息
*/
public List listObjects() {
Iterable> results = minioClient.listObjects(
ListObjectsArgs.builder().bucket(bucketName).build());
List fileNames = new ArrayList<>();
try {
for (Result- result : results) {
Item item = result.get();
fileNames.add(item.objectName());
}
} catch (Exception e) {
e.printStackTrace();
return null;
}
return fileNames;
}
/**
* 删除
*
* @param fileName BucketName 需要传入桶名
* @return
* @throws Exception
*/
public boolean remove(String fileName) {
try {
minioClient.removeObject(RemoveObjectArgs.builder().bucket("BucketName").object(fileName).build());
} catch (Exception e) {
return false;
}
return true;
}
/**
* 查看文件内容
*
* bucketName 桶名
*
* @param fileName 文件名称(包含扩展名)
* @return 文件内容字符串
*/
public byte[] viewFile(String fileName) {
try {
GetObjectArgs objectArgs = GetObjectArgs.builder()
.bucket(bucketName)
.object(fileName)
.build();
InputStream is = minioClient.getObject(objectArgs);
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
byte[] buffer = new byte[4096];
int bytesRead;
while ((bytesRead = is.read(buffer)) != -1) {
outputStream.write(buffer, 0, bytesRead);
}
return outputStream.toByteArray();
} catch (Exception e) {
log.error("查看文件内容失败: " + e.getMessage(), e);
return null;
}
}
}
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
import java.util.List;
/**
* MinIO操作的Controller。
*
* @Author: xufan_yang
* @Date 2023/7/24/024 14:22
*/
@RestController
@RequestMapping("/minio")
public class MinioController {
@Autowired
private MinioUtil minioUtil;
/**
* 检查存储桶是否存在。
*
* @param bucketName 要检查的桶名。
* @return 存在返回true,否则返回false。
*/
@GetMapping("/bucketExists")
public Boolean bucketExists(@RequestParam("bucketName") String bucketName) {
return minioUtil.bucketExists(bucketName);
}
/**
* 创建存储桶。
*
* @param bucketName 要创建的桶名。
* @return 成功创建返回true,否则返回false。
*/
@PostMapping("/makeBucket")
public Boolean makeBucket(@RequestParam("bucketName") String bucketName) {
return minioUtil.makeBucket(bucketName);
}
/**
* 上传文件到MinIO。
*
* @param file 要上传的文件。
* @return ResponseEntity,如果上传成功返回成功消息,否则返回错误消息。
*/
@PostMapping("/upload")
public ResponseEntity uploadFile(@RequestParam("file") MultipartFile file) {
try {
String objectName = minioUtil.upload(file);
return ResponseEntity.ok("文件上传成功 对象名:" + objectName);
} catch (Exception e) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("文件上传失败:" + e.getMessage());
}
}
/**
* 从MinIO下载文件。
*
* @param objectName 要下载的文件对象名。
* @param response HttpServletResponse用于将文件内容写入。
*/
@GetMapping("/download")
public void downloadFile(@RequestParam("objectName") String objectName, HttpServletResponse response) {
try {
minioUtil.download(objectName, response);
} catch (Exception e) {
response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());
response.setContentType("text/plain");
try {
response.getWriter().write("文件下载失败:" + e.getMessage());
} catch (IOException ioException) {
ioException.printStackTrace();
}
}
}
/**
* 从MinIO删除文件。
*
* @param objectName 要删除的文件对象名。
* @return ResponseEntity,如果删除成功返回成功消息,否则返回错误消息。
*/
@DeleteMapping("/delete")
public ResponseEntity deleteFile(@RequestParam("objectName") String objectName) {
try {
minioUtil.remove(objectName);
return ResponseEntity.ok("文件删除成功。对象名:" + objectName);
} catch (Exception e) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("文件删除失败:" + e.getMessage());
}
}
/**
* 列出MinIO存储桶中的所有文件。
*
* @return ResponseEntity,如果成功返回文件名列表,否则返回错误消息。
*/
@GetMapping("/list")
public ResponseEntity> listFiles() {
try {
List fileNames = minioUtil.listObjects();
// 对文件名进行URL解码,恢复成原始的中文字符
for (int i = 0; i < fileNames.size(); i++) {
String decodedFileName = URLDecoder.decode(fileNames.get(i), String.valueOf(StandardCharsets.UTF_8));
fileNames.set(i, decodedFileName);
}
return ResponseEntity.ok(fileNames);
} catch (Exception e) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(null);
}
}
/**
* 查看文件内容
*
* bucketName 桶名
* @param fileName 文件名称(包含扩展名)
* @return 文件内容字符串
*/
@GetMapping("/viewFile")
public void viewImage(@RequestParam("fileName") String fileName, HttpServletResponse response) {
try {
byte[] imageData = minioUtil.viewFile(fileName);
if (imageData != null) {
response.setContentType("image/jpeg"); // 设置响应的内容类型为图片
response.getOutputStream().write(imageData);
} else {
response.setStatus(HttpStatus.NOT_FOUND.value());
}
} catch (Exception e) {
response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());
e.printStackTrace();
}
}
}
恭喜!您已经成功地搭建了一个私有对象存储服务并学会了如何使用MinIO来上传和下载对象。MinIO是一个功能强大且易于使用的开源工具,适用于构建各种应用程序,包括数据备份、图像存储和大规模数据分析等。
请注意,本教程只涵盖了MinIO的基本功能。您可以进一步深入研究MinIO的高级特性,并根据您的需求进行配置和定制化。希望您享受使用MinIO搭建私有对象存储服务的过程!