目录
曾几何时,之前说的搭建分布式文件存储系统,好像唯一的选择就是 fastDfs,慢慢的发现好像周围的团队和项目都在选择Minio,进入了视野。
Minio与Amazon S3云存储服务兼容,采用Golang实现,服务端支持Windows、Linux、 OS X和FreeBSD等操作系统;客户端支持Java、Python、Javacript、 Golang语言,下面会有基于java的sdk的相关集成和操作。
Minio是Apache License v2.0下发布的对象存储服务器。它与Amazon S3云存储服务兼容。它最适合存储非结构化数据,如照片,视频,日志文件,备份和容器/ VM映像。对象的大小可以从几KB到最大5TB。。。
MinIO中国站点:http://www.minio.org.cn
MinIO中国镜像站:http://dl.minio.org.cn
Minio Service的搭建
1、Docker方式搭建
注意:minio新版本的 API和console端口不能使用同一个,否则会报错:
ERROR Unable to start the server: --console-address cannot be same as --address
docker的安装过程可以参考:Docker的基本概念、环境搭建、常用命令
docker安装 nimio:docker pull minio/minio
启动minio,由于minio会创建动态的端口,并且外网不能进行访问,所以使用以下命令(其中 9000是 web的端口,9001的api的端口,新版本 java sdk不能使用 9000web端口):
- docker run minio服务端;
- -p 9000:9000 --console-address ":9000" 挂载9000端口为web端口;
- -p 9001:9001 --address ":9001" 挂载9001端口为客户端(如Java客户端)访问的端口;
- -e 设置9000端口web访问的 账号和密码;
- -v 挂载数据和配置地址;
docker run -p 9000:9000 -p 9001:9001 --name minio \
-d --restart=always \
-e "MINIO_ACCESS_KEY=mosty" \
-e "MINIO_SECRET_KEY=mosty123456" \
-v /home/data:/data \
-v /home/config:/root/.minio \
minio/minio server --console-address ":9000" --address ":9001" /data
查看:docker ps -a
使用 docker logs contrain_id 命令查看日志信息:
此时,如果防火墙、或者阿里云或者华为云的安全组规则添加端口,则可以在浏览器使用外网访问,如: http://外网ip:9000/login
登陆后创建 对应的的bucket, 并设置为可以读写权限;就可以客户端就可以访问了;当然客户端也有一点的api创建bucket。。。
2、二进制包安装[不推荐]
说明:下面的官方二进制包拉去会非常慢,也可以使用国内镜像,只是国内镜像已经换成rpm包了
wget https://dl.min.io/server/minio/release/linux-amd64/minio
chmod +x minio
./minio server /data
3、国内镜像rpm安装
国内镜像地址:https://dl.min.io/server/minio/release/linux-amd64/
wget https://dl.min.io/server/minio/release/linux-amd64/minio-20220326064928.0.0.x86_64.rpm
rpm -ivh minio-20220326064928.0.0.x86_64.rpm
安装完成可以使用 systemctl status minio 查看状态:
其他rpm包此时就可以使用 systemctl start minio进行启动了,只是此时不能进行启动需要修改 /etc/systemd/system/minio.service文件,将linux的用户名和group修改(可以修改为 root root)
可以修改文件 /etc/default/minio 填写访问的账号密码等,直接将该配置地址也修改为 minio.conf,应该是没有改配置文件的自行创建
touch /etc/default/minio.conf
vi /etc/default/minio.conf
MINIO_VOLUMES="/opt/minio/data"
MINIO_OPTS="--address :9001 --console-address :9000"
MINIO_ACCESS_KEY=kevin
MINIO_SECRET_KEY=kevin123
重新刷新,启动,查看状态:
systemctl daemon-reload
systemctl start minio
systemctl status minio
自行设置Centos防火墙端口,如果是阿里云等请修改安全规则,然后访问 http://ip:9999, 并且我当前的版本使用 docker和rpm安装的界面都不一样(版本不一样)
Java客户端操作
基于spring boot项目,需要引入 minio客户端相关的maven包,
<dependency>
<groupId>io.minio</groupId>
<artifactId>minio</artifactId>
<version>8.0.3</version>
</dependency>
下面三个上传图片接口,分别是上传到文件的bucket、上传到图片的bubcket、已经客户端指定桶进行上传(前提是已经创建桶并且开放了权限),上传成功返回可以直接通过外网等访问的URL类似于使用阿里云的OSS,代码和页面样式如下:
import com.mosty.common.base.domain.ResponseResult;
import com.mosty.common.base.exception.Asserts;
import com.mosty.common.core.config.MinIoClientConfig;
import io.minio.BucketExistsArgs;
import io.minio.GetPresignedObjectUrlArgs;
import io.minio.MinioClient;
import io.minio.PutObjectArgs;
import io.minio.errors.*;
import io.minio.http.Method;
import io.netty.util.internal.StringUtil;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.jetbrains.annotations.NotNull;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import java.io.IOException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.UUID;
/**
* 上传下载服务
* @author kevin
* @date 2022/3/7 10:46 AM
* @since 1.0.0
*/
@Slf4j
@RestController
@RequestMapping("/minio")
@AllArgsConstructor
@Api(tags = "图片上传服务")
@SuppressWarnings("unused")
public class UploadController {
private final MinioClient minioClient;
private final MinIoClientConfig minIoClientConfig;
/**
* 使用原文件名上传
* @param file 文件
* @return 上传结果
*/
@ResponseBody
@PostMapping("/image/source/upload")
@ApiOperation(value = "源文件名上传", httpMethod = "POST", response = Boolean.class)
public ResponseResult<Boolean> sourceUpload(MultipartFile file) {
try {
PutObjectArgs objectArgs = PutObjectArgs.builder().object(file.getOriginalFilename())
.bucket(minIoClientConfig.getImageBucket())
.contentType(file.getContentType())
.stream(file.getInputStream(), file.getSize(), -1).build();
minioClient.putObject(objectArgs);
return ResponseResult.success(Boolean.TRUE);
} catch (Exception e) {
e.printStackTrace();
log.error("上传失败", e);
return ResponseResult.fail(e.getMessage());
}
}
/**
* 上传图片
* @param file 图片
* @return 图片访问地址
* @throws Exception 上传结果
*/
@ResponseBody
@PostMapping("/image/upload")
@ApiOperation(value = "前端上传组件使用:上传图片(返回图片的访问地址)", httpMethod = "POST", response = String.class)
public ResponseResult<String> uploadImage(@RequestParam("file") MultipartFile file) throws Exception {
if (file.getSize() > 0) {
return uploadOssWithBucket(file, minIoClientConfig.getImageBucket());
}
return ResponseResult.fail("未选择任何文件,上传失败");
}
/**
* 上传文件
* @param file 文件
* @return 文件访问地址
* @throws Exception 异常
*/
@ResponseBody
@PostMapping("/file/upload")
@ApiOperation(value = "前端上传组件使用:上传文件(返回文件的访问地址)", httpMethod = "POST", response = String.class)
public ResponseResult<String> uploadFile(@RequestParam("file") MultipartFile file) throws Exception {
if (file.getSize() > 0) {
return uploadOssWithBucket(file, minIoClientConfig.getFileBucket());
}
return ResponseResult.fail("未选择任何文件,上传失败");
}
/**
* 上传文件
* @param file 文件
* @param bucketName 桶名称
* @return 访问地址
* @throws Exception 异常
*/
@ResponseBody
@PostMapping("/{bucketName}/upload")
@ApiOperation(value = "指定桶上传文件(返回文件的访问地址)", httpMethod = "POST", response = String.class)
public ResponseResult<String> upload(@RequestParam("file") MultipartFile file,
@PathVariable("bucketName") String bucketName) throws Exception {
Asserts.check(StringUtils.isEmpty(bucketName), "桶名称不能为空!");
boolean bucketExists = minioClient.bucketExists(BucketExistsArgs.builder().bucket(bucketName).build());
Asserts.check(!bucketExists, "桶%s不存在!", bucketName);
if (file.getSize() > 0) {
return uploadOssWithBucket(file, bucketName);
}
return ResponseResult.fail("未选择任何文件,上传失败");
}
@NotNull
private ResponseResult<String> uploadOssWithBucket(MultipartFile file, String bucketName) throws IOException, ErrorResponseException, InsufficientDataException, InternalException, InvalidKeyException, InvalidResponseException, NoSuchAlgorithmException, ServerException, XmlParserException {
// 原文件名
String fileName = file.getOriginalFilename();
if (StringUtils.isEmpty(fileName)) {
return ResponseResult.fail("文件名不能为空!");
}
// 文件类型
String suffixName = fileName.substring(fileName.lastIndexOf("."));
// 生成新文件名,确保唯一性
String objectName = UUID.randomUUID() + suffixName;
// 文件类型
String fileType = file.getContentType();
// 使用putObject上传一个文件到存储桶中
PutObjectArgs build = PutObjectArgs.builder().object(objectName)
.bucket(bucketName)
.contentType(file.getContentType())
.stream(file.getInputStream(), file.getSize(), -1).build();
minioClient.putObject(build);
// 得到文件 url
String url = minioClient.getPresignedObjectUrl(
GetPresignedObjectUrlArgs.builder()
.method(Method.GET)
.bucket(bucketName)
.object(objectName)
// .expiry(60 * 60 * 24 * 1000)
.build());
// if (!StringUtils.isEmpty(minIoClientConfig.getDownloadEndpoint())) {
// url = url.replace(minIoClientConfig.getEndpoint(), minIoClientConfig.getDownloadEndpoint());
// }
log.info("上传图片 OriginalFilename:{}, objectName:{}, url:{}", file, objectName, url);
return ResponseResult.success(url);
}
}
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;
/**
* minio配置
* @author kevin
* @date 2022/3/7 10:41 AM
* @since 1.0.0
*/
@Data
@Configuration
public class MinIoClientConfig {
@Value("${minio.endpoint:http://ip:9001}")
private String endpoint;
@Value("${minio.downloadEndpoint:http://ip:9000}")
private String downloadEndpoint;
@Value("${minio.accessKey:kevin}")
private String accessKey;
@Value("${minio.secretKey:kevin123}")
private String secretKey;
/** 图片桶名称 */
@Value("${minio.bucket.image:image}")
private String imageBucket;
/** 文件桶名称 */
@Value("${minio.bucket.file:file}")
private String fileBucket;
/**
* 注入minio 客户端
* @return
*/
@Bean
public MinioClient minioClient() {
return MinioClient.builder()
.endpoint(endpoint)
.credentials(accessKey, secretKey)
.build();
}
}