Linux+Docker:3分钟实现MinIO在线部署与Java集成
Linux下使用Docker安装MinIO
1. 拉取 MinIO 镜像
docker pull minio/minio
2. 创建挂载目录
mkdir -p /opt/minio/data
mkdir -p /optl/minio/config
3. 检查端口占用
sudo lsof -i :9000
...
4. 启动 MinIO 容器
docker run --name minio \ # 容器名称
-p 9010:9000 \ # 映射主机端口9010到容器端口9000
-p 9020:9020 \ # 映射主机端口9020到容器端口9020
-d --restart=always \ # 容器重启策略:系统启动时自动重启
-e "MINIO_ROOT_USER=admin" \ # MinIO 管理员用户名
-e "MINIO_ROOT_PASSWORD=admin2minio" \ # MinIO 管理员密码 这里的密码需要大于8位不如有报错
-v "/opt/minio/data:/data" \ # 数据存储目录
-v "/opt/minio/config:/root/.minio" \ # 配置文件目录
minio/minio server /data --console-address ":9020" # 启动MinIO服务并指定控制台端口
5. 配置防火墙(如果有需要)
sudo firewall-cmd --zone=public --add-port=9010/tcp --permanent
sudo firewall-cmd --reload
6. 查看容器内的 MinIO 服务状态
docker exec -it minio /bin/sh
netstat -tuln
7. 检查容器日志
docker logs minio
可能会遇到的问题
1. RequestTimeTooSkewed 错误:
问题:客户端请求的时间与服务器时间的差异过大。MinIO要求客户端和服务器的时间同步。
解决方案:确保客户端和服务器的系统时间同步,或手动调整时间。查看时间的命令:
linux时间查看命令:
date
2. MINIO_ROOT_PASSWORD 长度问题
问题: MinIO 对管理员密码有长度要求,密码必须至少 8 位字符,以保证安全性。短密码可能导致配置错误或安全问题。
解决方案:确保设置的 MINIO_ROOT_PASSWORD 至少为 8 位字符。检查密码是否符合 MinIO 的长度和复杂性要求。
- 停止 MinIO 容器:
docker stop minio
- 删除 MinIO 容器:
docker rm minio
- 重新运行 MinIO 容器:
docker run --name minio \
-p 9010:9000 \
-p 9020:9020 \
-d --restart=always \
-e "MINIO_ROOT_USER=admin" \
-e "MINIO_ROOT_PASSWORD=admin2minio" \
-v "/opt/minio/data:/data" \
-v "/opt/minio/config:/root/.minio" \
minio/minio server /data --console-address ":9020"
确保重新运行时,密码满足要求,并且所有路径和端口正确配置。
Java项目集成MinIO
1. 添加Maven依赖
<dependency>
<groupId>io.minio</groupId>
<artifactId>minio</artifactId>
<version>8.5.0</version>
</dependency>
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
<version>4.9.3</version>
</dependency>
2. 配置 application.yml
minio:
endpoint: http://ip:9010/ # MinIO服务的地址
accessKey: 'your-access-key' # MinIO的访问密钥
secretKey: 'your-secret-key' # MinIO的秘密密钥
bucketName: test # 默认的桶名称
3. 配置 MinIO 客户端
import io.minio.MinioClient;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class MinioConfig {
@Value("${minio.endpoint}")
private String endpoint;
@Value("${minio.accessKey}")
private String accessKey;
@Value("${minio.secretKey}")
private String secretKey;
@Bean
public MinioClient minioClient() {
return MinioClient.builder()
.endpoint(endpoint)
.credentials(accessKey, secretKey)
.build();
}
}
4. MinIO 工具类
import com.github.pagehelper.util.StringUtil;
import io.minio.*;
import io.minio.http.Method;
import io.minio.messages.DeleteError;
import io.minio.messages.DeleteObject;
import io.minio.messages.Item;
import org.apache.commons.io.IOUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
@Component
public class MinIoUtils {
@Autowired
private MinioClient minioClient;
@Value("${minio.bucketName}")
private String bucketName;
/**
* description: 判断bucket是否存在,不存在则创建
*
* @return: void
*/
public boolean existBucket(String name) {
boolean exists;
try {
exists = minioClient.bucketExists(BucketExistsArgs.builder().bucket(name).build());
if (!exists) {
minioClient.makeBucket(MakeBucketArgs.builder().bucket(name).build());
exists = true;
}
} catch (Exception e) {
e.printStackTrace();
exists = false;
}
return exists;
}
/**
* 创建存储bucket
*
* @param bucketName 存储bucket名称
* @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
*
* @param bucketName 存储bucket名称
* @return Boolean
*/
public Boolean removeBucket(String bucketName) {
try {
minioClient.removeBucket(RemoveBucketArgs.builder()
.bucket(bucketName)
.build());
} catch (Exception e) {
e.printStackTrace();
return false;
}
return true;
}
/**
* description: 上传文件
*
* @param file
* @return: java.lang.String
*/
public String upload(MultipartFile file, String fileName, String bucketNameStr) {
InputStream in = null;
try {
if (StringUtil.isEmpty(bucketNameStr)) {
bucketNameStr = bucketName;
}
in = file.getInputStream();
minioClient.putObject(PutObjectArgs.builder()
.bucket(bucketNameStr)
.object(fileName)
.stream(in, in.available(), -1)
.contentType(file.getContentType())
.build()
);
} catch (Exception e) {
e.printStackTrace();
} finally {
if (in != null) {
try {
in.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return bucketNameStr + "/" + fileName;
}
/**
* description: 下载文件
*
* @param fileName
* @return: org.springframework.http.ResponseEntity<byte [ ]>
*/
public ResponseEntity<byte[]> download(String bucketNameStr, String fileName) {
ResponseEntity<byte[]> responseEntity = null;
InputStream in = null;
ByteArrayOutputStream out = null;
try {
if (StringUtil.isEmpty(bucketNameStr)) {
bucketNameStr = bucketName;
}
in = minioClient.getObject(GetObjectArgs.builder().bucket(bucketName).object(fileName).build());
out = new ByteArrayOutputStream();
IOUtils.copy(in, out);
//封装返回值
byte[] bytes = out.toByteArray();
HttpHeaders headers = new HttpHeaders();
try {
headers.add("Content-Disposition", "attachment;filename=" + URLEncoder.encode(fileName, "UTF-8"));
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
headers.setContentLength(bytes.length);
headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);
headers.setAccessControlExposeHeaders(Arrays.asList("*"));
responseEntity = new ResponseEntity<byte[]>(bytes, headers, HttpStatus.OK);
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (in != null) {
try {
in.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (out != null) {
out.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
return responseEntity;
}
/**
* 查看文件对象
*
* @param bucketName 存储bucket名称
* @return 存储bucket内文件对象信息
*/
public List<ObjectItem> listObjects(String bucketName) {
Iterable<Result<Item>> results = minioClient.listObjects(
ListObjectsArgs.builder().bucket(bucketName).build());
List<ObjectItem> objectItems = new ArrayList<>();
try {
for (Result<Item> result : results) {
Item item = result.get();
ObjectItem objectItem = new ObjectItem();
objectItem.setObjectName(item.objectName());
objectItem.setSize(item.size());
objectItems.add(objectItem);
}
} catch (Exception e) {
e.printStackTrace();
return null;
}
return objectItems;
}
/**
* 批量删除文件对象
*
* @param bucketName 存储bucket名称
* @param objects 对象名称集合
*/
public Iterable<Result<DeleteError>> removeObjects(String bucketName, List<String> objects) {
List<DeleteObject> dos = objects.stream().map(e -> new DeleteObject(e)).collect(Collectors.toList());
Iterable<Result<DeleteError>> results = minioClient.removeObjects(RemoveObjectsArgs.builder().bucket(bucketName).objects(dos).build());
return results;
}
/**
* 根据文件名和桶获取文件路径
*
* @param bucketName 存储bucket名称
*/
public String getFileUrl(String bucketName, String objectFile) {
try {
if(StringUtil.isEmpty(bucketName)){
bucketName = this.bucketName;
}
return minioClient.getPresignedObjectUrl(GetPresignedObjectUrlArgs.builder()
.method(Method.GET)
.bucket(bucketName)
.object(objectFile)
.build()
);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}
5. api
import com.alibaba.fastjson.JSONObject;
import com.njmh.authority.utils.MinIoUtils;
import com.njmh.authority.utils.ObjectItem;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import java.util.List;
@RestController
@RequestMapping("/minio")
public class MinioApi {
@Autowired
private MinIoUtils minioUtils;
@Value("${minio.endpoint}")
private String address;
@Value("${minio.bucketName}")
private String bucketName;
@PostMapping("/upload")
public Object upload(@RequestParam(value = "file") MultipartFile file, @RequestParam("bucketName") String bucketName) {
return address + "/" + minioUtils.upload(file, file.getOriginalFilename(), bucketName);
}
@PostMapping("/getListByBucket")
public List<ObjectItem> getListByBucket() {
List<ObjectItem> list = minioUtils.listObjects(bucketName);
return list;
}
@PostMapping("/existBucket")
public boolean existBucket(@RequestBody JSONObject jsonObject) {
return minioUtils.existBucket(jsonObject.getString("name"));
}
@PostMapping("/makeBucket")
public boolean makeBucket(@RequestBody JSONObject jsonObject) {
return minioUtils.makeBucket(jsonObject.getString("name"));
}
@PostMapping("/removeBucket")
public boolean removeBucket(@RequestBody JSONObject jsonObject) {
return minioUtils.removeBucket(jsonObject.getString("name"));
}
@PostMapping("/getFileUrl")
public String getFileUrl(@RequestBody JSONObject jsonObject) {
return minioUtils.getFileUrl(jsonObject.getString("bucketName"),jsonObject.getString("fileName"));
}
@GetMapping("/loadFile")
@ResponseBody
public ResponseEntity<?> loadFile(@RequestParam("bucketName") String bucketName,
@RequestParam("filePath") String filePath) {
return minioUtils.download(bucketName,filePath);
}
}