使用docker-compose部署Minio,并整合Spring boot实现文件上传
部署环境
centos7.9搭建
Docker、Docker-compose 安装
Minio安装
docker-compose文件配置
在centos系统中创建minio目录,将docker-compose.yaml文件放在minio目录下。
version: "3"
services:
minio:
image: quay.io/minio/minio
container_name: minio
hostname: minio
privileged: true
restart: always
environment:
TZ: Asia/Shanghai
# 账号
MINIO_ROOT_USER: topfus
# 密码
MINIO_ROOT_PASSWORD: topfus123
ports:
- 9000:9000
- 9090:9090
volumes:
- ./minio/data:/data
- ./minio/config:/root/.minio
command: server /data --console-address ":9090"
networks:
minio-network:
ipv4_address: 172.28.0.5
networks:
minio-network:
driver: bridge
ipam:
config:
- subnet: 172.28.0.0/24
name: minio-network
运行
docker-compose up -d
Minio使用Nginx反向代理
nginx配置
upstream minio_server {
least_conn; # 表示将请求传递给活跃度最低的server
server 127.0.0.1:9000;
}
upstream minio_console {
least_conn;
server 127.0.0.1:9090;
}
server {
# listen 443 ssl;
listen 80;
listen [::]:80;
server_name server.example.com;
charset utf-8;
client_max_body_size 1024m; # 上传文件大小限制,默认是1M
client_body_buffer_size 300k; # 数据流传输大小
# ssl_certificate /data/minio/ssl/server.example.com.pem;
# ssl_certificate_key /data/minio/ssl/server.example.com.key;
# ssl_session_timeout 10m;
# ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
# ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:HIGH:!aNULL:!MD5:!RC4:!DHE;
# ssl_prefer_server_ciphers on;
ignore_invalid_headers off; # 标头中允许特殊字符
# 禁用缓冲
proxy_buffering off;
proxy_request_buffering off;
location / {
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_connect_timeout 300;
proxy_http_version 1.1;
proxy_set_header Connection "";
chunked_transfer_encoding off;
proxy_pass http://minio_server/;
}
}
server {
listen 80;
listen [::]:80;
server_name console.example.com;
# 标头中允许特殊字符
ignore_invalid_headers off;
client_max_body_size 1024m; # 上传文件大小限制,默认是1M
client_body_buffer_size 300k; # 数据流传输大小
# 禁用缓冲
proxy_buffering off;
proxy_request_buffering off;
location / {
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-NginX-Proxy true;
# 这是传递要哈希的正确 IP 所必需的
# 注意:这个配置需要安装http_realip_module模块
real_ip_header X-Real-IP;
proxy_connect_timeout 300;
# 支持websocket
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
chunked_transfer_encoding off;
proxy_pass http://minio_console/;
}
}
安装realip模块
./configure --prefix=/usr/local/nginx --with-http_stub_status_module --with-pcre=../pcre-6.6 --with-http_realip_module
make
make install
以上nginx配置好后,直接访问域名console.example.com后会跳转console.example.com:9090,如图:
整合Spring Boot实现文件上传
引入Minio依赖包
<!-- https://mvnrepository.com/artifact/io.minio/minio -->
<dependency>
<groupId>io.minio</groupId>
<artifactId>minio</artifactId>
<version>8.5.7</version>
</dependency>
创建配置文件
application.yml配置
minio:
bucketname: zhush-bucket
endpoint: http://127.0.0.1:9000/
accesskey: minioadmin
secretkey: minioadmin
创建MinioConfig配置类
package com.zhush.service.config;
import io.minio.MinioClient;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* @ClassName MinioConfig
* @Description TODO
* @Author zhush
* @Date 2023/12/27 0027 9:52
**/
@Data
@Configuration
@ConfigurationProperties(prefix = "minio")
public class MinioConfig {
private String bucketname;
private String endpoint;
private String accesskey;
private String secretkey;
@Bean
public MinioClient minioClient() {
return MinioClient.builder()
.endpoint(endpoint)
.credentials(accesskey, secretkey)
.build();
}
}
封装MinioUtils工具类
package com.zhush.service.utils;
import cn.hutool.core.date.DateUtil;
import com.zhush.service.config.MinioConfig;
import io.minio.*;
import io.minio.http.Method;
import io.minio.messages.Bucket;
import io.minio.messages.DeleteError;
import io.minio.messages.DeleteObject;
import io.minio.messages.Item;
import jakarta.annotation.Resource;
import jakarta.servlet.ServletOutputStream;
import jakarta.servlet.http.HttpServletResponse;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.util.FastByteArrayOutputStream;
import org.springframework.web.multipart.MultipartFile;
import java.util.*;
/**
* @ClassName MinioUtils
* @Description minio对象存储工具类
* @Author zhush
* @Date 2023/12/27 0027 9:54
**/
@Slf4j
@Component
public class MinioUtils {
@Resource
private MinioConfig minioConfig;
@Resource
private MinioClient minioClient;
/**
* 创建
* @param bucketName
*/
@SneakyThrows
public void makeBucketName(String bucketName) {
minioClient.makeBucket(MakeBucketArgs.builder().bucket(bucketName).build());
}
/**
* 删除
* @param bucketName
*/
@SneakyThrows
public void removeBucketName(String bucketName) {
minioClient.removeBucket(RemoveBucketArgs.builder().bucket(bucketName).build());
}
/**
* 验证是否存在
* @param bucketName
* @return
*/
@SneakyThrows
public Boolean bucketExists(String bucketName) {
return minioClient.bucketExists(BucketExistsArgs.builder().bucket(bucketName).build());
}
/**
* 获取bucket列表
* @return
*/
@SneakyThrows
public List<Bucket> getBucketList() {
return minioClient.listBuckets();
}
/**
* 文件上传
* @param file
* @return 返回文件路径
*/
@SneakyThrows
public String upload(MultipartFile file) {
// 获取上传文件名称
String originalFilename = file.getOriginalFilename();
// 根据年月日创建data_dir
String dataDir = DateUtil.format(new Date(), "yyyyMMdd");
// 使用uuid生成新的文件名称
String uuid = UUID.randomUUID().toString().replaceAll("-", "");
String fileName = dataDir + "/" + uuid + originalFilename;
minioClient.putObject(PutObjectArgs.builder()
.bucket(minioConfig.getBucketname())
.object(fileName)
.stream(file.getInputStream(), file.getSize(), -1)
.build());
return minioConfig.getEndpoint() + "/" + minioConfig.getBucketname() + "/" + fileName;
}
/**
* 批量文件上传
* @param files
* @return
*/
@SneakyThrows
public List<String> uploads(MultipartFile[] files) {
List<String> fileUrls = new ArrayList<>();
for (MultipartFile file : files) {
// 获取上传文件名称
String originalFilename = file.getOriginalFilename();
// 根据年月日创建data_dir
String dataDir = DateUtil.format(new Date(), "yyyyMMdd");
// 使用uuid生成新的文件名称
String uuid = UUID.randomUUID().toString().replaceAll("-", "");
String fileName = dataDir + "/" + uuid + originalFilename;
minioClient.putObject(PutObjectArgs.builder()
.bucket(minioConfig.getBucketname())
.object(fileName)
.stream(file.getInputStream(), file.getSize(), -1)
.build());
String fileUrl = minioConfig.getEndpoint() + "/" + minioConfig.getBucketname() + "/" + fileName;
fileUrls.add(fileUrl);
}
return fileUrls;
}
/**
* 预览
* @param fileName
* @return
*/
@SneakyThrows
public String preview(String fileName) {
return minioClient.getPresignedObjectUrl(GetPresignedObjectUrlArgs.builder()
.bucket(minioConfig.getBucketname())
.object(fileName)
.method(Method.GET)
.build());
}
/**
* 下载
* @param fileName
* @param response
*/
@SneakyThrows
public void download(String fileName, HttpServletResponse response) {
GetObjectResponse objectResponse = minioClient.getObject(GetObjectArgs.builder()
.bucket(minioConfig.getBucketname())
.object(fileName)
.build());
byte[] bytes = new byte[1024];
int len;
FastByteArrayOutputStream outputStream = new FastByteArrayOutputStream();
while ((len = objectResponse.read(bytes)) != -1) {
outputStream.write(bytes, 0, len);
}
outputStream.close();
byte[] byteArray = outputStream.toByteArray();
response.setCharacterEncoding("utf-8");
response.setContentType("application/force-download"); // 设置强制下载不打开
response.setHeader("Access-Control-Expose-Headers", "Content-Disposition");
response.addHeader("Content-Disposition", "attachment;fileName=" + fileName);
ServletOutputStream stream = response.getOutputStream();
stream.write(byteArray);
stream.flush();
}
/**
* 获取文件对象
* @return
*/
@SneakyThrows
public List<Item> getItemObjects() {
Iterable<Result<Item>> results = minioClient.listObjects(ListObjectsArgs.builder()
.bucket(minioConfig.getBucketname())
.build());
List<Item> items = new ArrayList<>();
for (Result<Item> item : results) {
items.add(item.get());
}
return items;
}
/**
* 删除文件
* @param fileName
*/
@SneakyThrows
public void removeFile(String fileName) {
minioClient.removeObject(RemoveObjectArgs.builder()
.bucket(minioConfig.getBucketname())
.object(fileName)
.build());
}
/**
* 批量删除文件
* @param fileNameList
*/
@SneakyThrows
public void removeFiles(List<String> fileNameList) {
List<DeleteObject> deleteObjects = new LinkedList<>();
for (String objectName : fileNameList) {
deleteObjects.add(new DeleteObject(objectName));
}
Iterable<Result<DeleteError>> results = minioClient.removeObjects(RemoveObjectsArgs.builder()
.bucket(minioConfig.getBucketname())
.objects(deleteObjects)
.build());
for (Result<DeleteError> result : results) {
DeleteError error = result.get();
log.error("Error in deleting object " + error.objectName() + "; " + error.message());
}
}
}