最近项目中使用Minio图床服务器实现图片上传,在此做一个记录,项目中的环境如下:
Nacos、Gradle、springBoot、mybatis、MySQL…
首先,需要在Gradle中添加Minio的依赖,此项目中使用的是3.0.10版本
compile 'io.minio:minio:3.0.10'
然后在项目中添加MinioUtils的配置类,旨在调用Minio的服务,提供调用Minio上传图片的接口,项目中所需参数都写在了Nacos配置中心,因此使用@NacosValue的注解形式从Nacos配置文件中拿取对应的参数,调用上传图片的接口,返回一个 Minio域名/桶名/存入桶中的文件名 的url
import com.alibaba.nacos.api.config.annotation.NacosValue;
import com.iid.common.helper.IdHelper;
import io.minio.MinioClient;
import io.minio.errors.*;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.xmlpull.v1.XmlPullParserException;
import javax.annotation.PostConstruct;
import java.io.IOException;
import java.io.InputStream;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
/**
* @ClassName MinioUtils
* @Description: TODO
* @Author XuJianSong
* @Date 2021-01-07
* @Version V1.0
**/
@Slf4j
@Component
public class MinioUtils {
private MinioClient minioClient;
@NacosValue(value = "${ymukj.minio.endpoint}")
private String endPoint;//Minio的域名
@NacosValue(value = "${ymukj.minio.accessKey}")
private String accessKey;//Minio登录名
@NacosValue(value = "${ymukj.minio.secretKey}")
private String secretKey;//Minio登陆密码
@NacosValue(value = "${ymukj.minio.preUrl}")
private String preUrl;//图床图片的域名(带bucket的)
@PostConstruct
public void initMinioClient() {//调用Minio服务
try {
minioClient = new MinioClient(endPoint, accessKey, secretKey);
} catch (InvalidEndpointException e) {
log.error(e.getMessage(), e);
} catch (InvalidPortException e) {
log.error(e.getMessage(), e);
}
}
public String uploadFile(String bucketName, String objName, InputStream inputStream, Long lenght, String contentType) {
// 使用putObject上传一个文件到存储桶中。
try {
minioClient.putObject(bucketName, objName, inputStream, lenght, contentType);
boolean isExist = minioClient.bucketExists(bucketName);
if (!isExist) {
minioClient.makeBucket(bucketName);
}
return preUrl+"/"+objName;
} catch (Exception e) {
e.printStackTrace();
log.info(">>>>>>>>>>>>>>>>>>>>>>>>>Error:" + e);
return null;
}
}
}
在Minio中是有“桶(bucket)”的概念的,所谓的桶,就是在Minio的图床上的文件夹,如果传参的桶名称在图床上已存在,上传的图片就会存入当前桶中;
如果传参的桶名称在图床上不存在,源码方法中会先创建桶,再存入
如图所示:左边的就是Minio图床中的桶,右边就是桶中的文件,Minio是支持上传所有文件的,视频、文档文件都可以,不过项目中没有涉及到这些功能,待后续再研究
接下来就是在项目中写上传图片的接口了,因为不涉及的数据库的操作,所以我直接在controller层中把接口全部写完了,没有再去调用service层的方法
Controller层接口接收前端的文件传参,再根据MinioUtils配置类中,上传图片的方法接收参数的要求,对文件进行处理,首先我们能看到MinioUtils配置文件中的上传图片接口要求接收的参数是bucket, objName, inputStream, lenght, contentType这四个,这四个参数分别是:桶名、上传并保存到桶的文件名,文件的输入流,文件的类型。上传成功的话会返回一个url,将这个url放到浏览器中可以直接打开这个图片,项目中如果需要使用这张图片,可以直接将这个url存入到数据库中,后面可以从数据库中直接取用
public GlobalResponse uploadPic(MultipartFile file) {
String bucket = "pic";
String filename = file.getOriginalFilename();
String[] exts = filename.split("\\.");
String ext = exts[exts.length - 1];
String caselsh = filename.substring(0,filename.lastIndexOf("."));
String objName = SystemHelper.now() + caselsh + "." + ext;
log.info(">>>>>>>>>>>>>>>>>>>>>>>>>>objName:" + objName);
String contentType = file.getContentType();
InputStream inputStream = null;
Long lenght = null;
try {
inputStream = file.getInputStream();
lenght = Long.valueOf(inputStream.available());
} catch (IOException e) {
e.printStackTrace();
}
String picUrl = minioUtils.uploadFile(bucket, objName, inputStream, lenght, contentType);
log.info(">>>>>>>>>>>>>>>>>>>>>>>>>>picUrl:" + picUrl);
return GlobalResponse.success(picUrl);
}
这里需要注意的是上传到桶的文件名不要重复!不要重复!不要重复!
因为重复的文件名会导致一个问题:比如先上传一个图片A.png,存到桶中的名称是111.png,那么Minio域名/桶名/111.png就能直接打开文件A.png这张图,但是再上传一个图片B.png,存到同种的名称仍然是111.png,那么Minio域名/桶名/111.png打开的就是B.png这张图,如果图片A.png已经存入数据库,并已经使用的话,后果可想而知。
因此接口中使用的解决方案是,使用时间戳+文件原名称+后缀的形式来命名,因为时间戳是13位毫秒级的,因此就算是上传同名的文件,也不会有存到桶中的文件名会重复的问题
本次的分享就到这里,如有错误欢迎大家来指正!