docker安装minio并在java项目中使用
对象存储
对象存储服务(Object Storage Service,OSS)是一种海量、安全、低成本、高可靠的云存储服务,适合存放任意类型的文件。容量和处理能力弹性扩展,多种存储类型供选择,全面优化存储成本。
minio的安装
minio的介绍:minio不仅可以作为对象存储使用,还可以作为云上对象存储服务的网关层,无缝对接到 Amazon S3、MicroSoft Azure。轻量级,开源
minio官网:https://www.minio.org.cn/
1.在docker中拉取minio的镜像
docker pull minio/minio
查看镜像
docker images
3.创建Minio容器并运行
docker run -p 9000:9000 -p 9090:9090 --name minio \
-v /opt/minio/data:/data \
-v /opt/minio/config:/root/.minio \
-e "MINIO_ROOT_USER=minio-admin" \
-e "MINIO_ROOT_PASSWORD=12345678" \
-d minio/minio server /data --console-address ":9090"
docker run -d -p 9000:9000 -p 9090:9090 --name=minio --restart=always --privileged=true -e "MINIO_ROOT_USER=admin" -e "MINIO_ROOT_PASSWORD=admin123456" -v /home/data:/data -v /home/config:/root/.minio minio/minio server /data --console-address ":9000" --address ":9090"
最后到http://你的ip:9090/login 使用上面的用户名和密码登录
这里如果你使用的是云服务器一定要到安全组去设置这个端口可以访问
4.springboot整合minio
1.引入依赖
<dependency>
<groupId>io.minio</groupId>
<artifactId>minio</artifactId>
<version>8.3.4</version>
</dependency>
2.配置文件
注意这个如果使用9000端口是console的地址
会出现错误:
io.minio.errors.InvalidResponseException: Non-XML response from server. Response code: 403, Content-Type: text/xml; charset=utf-8, body: <?xml version="1.0" encoding="UTF-8"?>
AccessDenied
S3 API Request made to Console port. S3 Requests should be sent to API port.
0
解决方法:使用api的地址9090的端口
# minio文件上传
minio:
minio_url: http://ip:9090
minio_name: xxx
minio_pass: xxx
bucketName: xxx
3.上传和下载
MinioConfig:获取配置文件中的minio信息
package com.picture.picturemap.config;
import com.picture.picturemap.constants.CommonConstant;
import com.picture.picturemap.constants.SymbolConstant;
import com.picture.picturemap.utils.MinioUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* Minio文件上传配置文件
* @author: jeecg-boot
*/
@Slf4j
@Configuration
@ConditionalOnProperty(prefix = "jeecg.minio", name = "minio_url")
public class MinioConfig {
@Value(value = "${minio.minio_url}")
private String minioUrl;
@Value(value = "${minio.minio_name}")
private String minioName;
@Value(value = "${minio.minio_pass}")
private String minioPass;
@Value(value = "${minio.bucketName}")
private String bucketName;
@Bean
public void initMinio(){
if(!minioUrl.startsWith(CommonConstant.STR_HTTP)){
minioUrl = "http://" + minioUrl;
}
if(!minioUrl.endsWith(SymbolConstant.SINGLE_SLASH)){
minioUrl = minioUrl.concat(SymbolConstant.SINGLE_SLASH);
}
MinioUtil.setMinioUrl(minioUrl);
MinioUtil.setMinioName(minioName);
MinioUtil.setMinioPass(minioPass);
MinioUtil.setBucketName(bucketName);
}
}
minio上传的工具类
CommonUtil
package com.picture.picturemap.utils;
import com.picture.picturemap.constants.CommonConstant;
import com.picture.picturemap.exception.BusinessException;
import com.picture.picturemap.exception.ErrorCode;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.multipart.MultipartFile;
@Slf4j
public class CommonUtil {
/**
* 文件名 正则字符串
* 文件名支持的字符串:字母数字中文.-_()() 除此之外的字符将被删除
*/
private static String FILE_NAME_REGEX = "[^A-Za-z\\.\\(\\)\\-()\\_0-9\\u4e00-\\u9fa5]";
/**
* 判断文件名是否带盘符,重新处理
*
* @param fileName
* @return
*/
public static String getFileName(String fileName) {
//判断是否带有盘符信息
// Check for Unix-style path
int unixSep = fileName.lastIndexOf('/');
// Check for Windows-style path
int winSep = fileName.lastIndexOf('\\');
// Cut off at latest possible point
int pos = (winSep > unixSep ? winSep : unixSep);
if (pos != -1) {
// Any sort of path separator found...
fileName = fileName.substring(pos + 1);
}
//替换上传文件名字的特殊字符
fileName = fileName.replace("=", "").replace(",", "").replace("&", "")
.replace("#", "").replace("“", "").replace("”", "");
//替换上传文件名字中的空格
fileName = fileName.replaceAll("\\s", "");
//update-beign-author:taoyan date:20220302 for: /issues/3381 online 在线表单 使用文件组件时,上传文件名中含%,下载异常
fileName = fileName.replaceAll(FILE_NAME_REGEX, "");
//update-end-author:taoyan date:20220302 for: /issues/3381 online 在线表单 使用文件组件时,上传文件名中含%,下载异常
return fileName;
}
/**
* 统一全局上传
*
* @Return: java.lang.String
*/
public static String upload(MultipartFile file, String bizPath) {
String url = "";
try {
url = MinioUtil.upload(file, bizPath);
} catch (Exception e) {
log.error(e.getMessage(), e);
throw new BusinessException(ErrorCode.SYS_FILE_UPLOAD_ERROR,e.getMessage());
}
return url;
}
}
//其中SymbolConstant.SINGLE_SLASH
public static final String SINGLE_SLASH = “/”;
package com.picture.picturemap.utils;
import com.picture.picturemap.constants.SymbolConstant;
import com.picture.picturemap.filter.SsrfFileTypeFilter;
import com.picture.picturemap.filter.StrAttackFilter;
import io.minio.*;
import io.minio.http.Method;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.multipart.MultipartFile;
import java.io.InputStream;
import java.net.URLDecoder;
/**
* minio文件上传工具类
*
* @author: jeecg-boot
*/
@Slf4j
public class MinioUtil {
private static String minioUrl;
private static String minioName;
private static String minioPass;
private static String bucketName;
public static void setMinioUrl(String minioUrl) {
MinioUtil.minioUrl = minioUrl;
}
public static void setMinioName(String minioName) {
MinioUtil.minioName = minioName;
}
public static void setMinioPass(String minioPass) {
MinioUtil.minioPass = minioPass;
}
public static void setBucketName(String bucketName) {
MinioUtil.bucketName = bucketName;
}
public static String getMinioUrl() {
return minioUrl;
}
public static String getBucketName() {
return bucketName;
}
private static MinioClient minioClient = null;
/**
* 上传文件
*
* @param file
* @return
*/
public static String upload(MultipartFile file, String bizPath, String customBucket) throws Exception {
String fileUrl = "";
//update-begin-author:wangshuai date:20201012 for: 过滤上传文件夹名特殊字符,防止攻击
bizPath = StrAttackFilter.filter(bizPath);
//update-end-author:wangshuai date:20201012 for: 过滤上传文件夹名特殊字符,防止攻击
//update-begin-author:liusq date:20210809 for: 过滤上传文件类型
SsrfFileTypeFilter.checkUploadFileType(file);
//update-end-author:liusq date:20210809 for: 过滤上传文件类型
String newBucket = bucketName;
if (oConvertUtils.isNotEmpty(customBucket)) {
newBucket = customBucket;
}
try {
initMinio(minioUrl, minioName, minioPass);
// 检查存储桶是否已经存在
if (minioClient.bucketExists(BucketExistsArgs.builder().bucket(newBucket).build())) {
log.info("Bucket already exists.");
} else {
// 创建一个名为ota的存储桶
minioClient.makeBucket(MakeBucketArgs.builder().bucket(newBucket).build());
log.info("create a new bucket.");
}
InputStream stream = file.getInputStream();
// 获取文件名
String orgName = file.getOriginalFilename();
if ("".equals(orgName)) {
orgName = file.getName();
}
orgName = CommonUtil.getFileName(orgName);
String objectName = bizPath + "/"
+ (orgName.indexOf(".") == -1
? orgName + "_" + System.currentTimeMillis()
: orgName.substring(0, orgName.lastIndexOf(".")) + "_" + System.currentTimeMillis() + orgName.substring(orgName.lastIndexOf("."))
);
// 使用putObject上传一个本地文件到存储桶中。
if (objectName.startsWith(SymbolConstant.SINGLE_SLASH)) {
objectName = objectName.substring(1);
}
PutObjectArgs objectArgs = PutObjectArgs.builder().object(objectName)
.bucket(newBucket)
.contentType("application/octet-stream")
.stream(stream, stream.available(), -1).build();
minioClient.putObject(objectArgs);
stream.close();
fileUrl = minioUrl + newBucket + "/" + objectName;
} catch (Exception e) {
log.error(e.getMessage(), e);
}
return fileUrl;
}
/**
* 文件上传
*
* @param file
* @param bizPath
* @return
*/
public static String upload(MultipartFile file, String bizPath) throws Exception {
return upload(file, bizPath, null);
}
/**
* 获取文件流
*
* @param bucketName
* @param objectName
* @return
*/
public static InputStream getMinioFile(String bucketName, String objectName) {
InputStream inputStream = null;
try {
initMinio(minioUrl, minioName, minioPass);
GetObjectArgs objectArgs = GetObjectArgs.builder().object(objectName)
.bucket(bucketName).build();
inputStream = minioClient.getObject(objectArgs);
} catch (Exception e) {
log.info("文件获取失败" + e.getMessage());
}
return inputStream;
}
/**
* 删除文件
*
* @param bucketName
* @param objectName
* @throws Exception
*/
public static void removeObject(String bucketName, String objectName) {
try {
initMinio(minioUrl, minioName, minioPass);
RemoveObjectArgs objectArgs = RemoveObjectArgs.builder().object(objectName)
.bucket(bucketName).build();
minioClient.removeObject(objectArgs);
} catch (Exception e) {
log.info("文件删除失败" + e.getMessage());
}
}
/**
* 获取文件外链
*
* @param bucketName
* @param objectName
* @param expires
* @return
*/
public static String getObjectUrl(String bucketName, String objectName, Integer expires) {
initMinio(minioUrl, minioName, minioPass);
try {
//update-begin---author:liusq Date:20220121 for:获取文件外链报错提示method不能为空,导致文件下载和预览失败----
GetPresignedObjectUrlArgs objectArgs = GetPresignedObjectUrlArgs.builder().object(objectName)
.bucket(bucketName)
.expiry(expires).method(Method.GET).build();
//update-begin---author:liusq Date:20220121 for:获取文件外链报错提示method不能为空,导致文件下载和预览失败----
String url = minioClient.getPresignedObjectUrl(objectArgs);
return URLDecoder.decode(url, "UTF-8");
} catch (Exception e) {
log.info("文件路径获取失败" + e.getMessage());
}
return null;
}
/**
* 初始化客户端
*
* @param minioUrl
* @param minioName
* @param minioPass
* @return
*/
private static MinioClient initMinio(String minioUrl, String minioName, String minioPass) {
if (minioClient == null) {
try {
minioClient = MinioClient.builder()
.endpoint(minioUrl)
.credentials(minioName, minioPass)
.build();
} catch (Exception e) {
e.printStackTrace();
}
}
return minioClient;
}
/**
* 上传文件到minio
*
* @param stream
* @param relativePath
* @return
*/
public static String upload(InputStream stream, String relativePath) throws Exception {
initMinio(minioUrl, minioName, minioPass);
if (minioClient.bucketExists(BucketExistsArgs.builder().bucket(bucketName).build())) {
log.info("Bucket already exists.");
} else {
// 创建一个名为ota的存储桶
minioClient.makeBucket(MakeBucketArgs.builder().bucket(bucketName).build());
log.info("create a new bucket.");
}
PutObjectArgs objectArgs = PutObjectArgs.builder().object(relativePath)
.bucket(bucketName)
.contentType("application/octet-stream")
.stream(stream, stream.available(), -1).build();
minioClient.putObject(objectArgs);
stream.close();
return minioUrl + bucketName + "/" + relativePath;
}
}
上传的controller
//SymbolConstant.SPOT_SINGLE_SLASH //public static final String SPOT_SINGLE_SLASH = “…/”;
//SymbolConstant.SPOT_DOUBLE_BACKSLASH //public static final String SPOT_DOUBLE_BACKSLASH = “…\”;
/**
* 文件上传统一方法
*
* @param request
* @param response
* @return
*/
@PostMapping(value = "/upload")
@ApiOperation(value = "通用模块-文件上传统一", notes = "通用模块-文件上传统一")
public BaseResponse<?> upload(HttpServletRequest request, HttpServletResponse response) throws Exception {
String savePath = "";
String bizPath = request.getParameter("biz");
//LOWCOD-2580 sys/common/upload接口存在任意文件上传漏洞
if (oConvertUtils.isNotEmpty(bizPath)) {
//public static final String SPOT_SINGLE_SLASH = "../";
//public static final String SPOT_DOUBLE_BACKSLASH = "..\\";
if (bizPath.contains(SymbolConstant.SPOT_SINGLE_SLASH) || bizPath.contains(SymbolConstant.SPOT_DOUBLE_BACKSLASH)) {
throw new BusinessException(ErrorCode.SYS_FILE_UPLOAD_ERROR,"Upload directory bizPath, format illegal!");
}
}
MultipartHttpServletRequest multipartRequest = (MultipartHttpServletRequest) request;
// 获取上传文件对象
MultipartFile file = multipartRequest.getFile("file");
if (oConvertUtils.isEmpty(bizPath)) {
bizPath = "";
}
//update-begin-author:taoyan date:20200814 for:文件上传改造
savePath = CommonUtil.upload(file, bizPath);
//update-end-author:taoyan date:20200814 for:文件上传改造
BaseResponse<?> result =null;
if (oConvertUtils.isNotEmpty(savePath)) {
result = ResultUtils.success(savePath);
} else {
result =ResultUtils.error(ErrorCode.SYS_FILE_UPLOAD_ERROR);
}
return result;
}