为什么用 minio ,因为 oss 收费。
minio 官网
maven minio
最新版依赖
<dependency>
<groupId>io.miniogroupId>
<artifactId>minioartifactId>
<version>8.4.5version>
dependency>
如果只添加minio依赖启动项目大概率会碰到如下的报错信息
***************************
APPLICATION FAILED TO START
***************************
Description:
An attempt was made to call a method that does not exist. The attempt was made from the following location:
io.minio.S3Base.>(S3Base.java:104)
The following method did not exist:
okhttp3.RequestBody.create([BLokhttp3/MediaType;)Lokhttp3/RequestBody;
The method's class, okhttp3.RequestBody, is available from the following locations:
jar:file:/D:/maven/repository/com/squareup/okhttp3/okhttp/3.14.4/okhttp-3.14.4.jar!/okhttp3/RequestBody.class
The class hierarchy was loaded from the following locations:
okhttp3.RequestBody: file:/D:/maven/repository/com/squareup/okhttp3/okhttp/3.14.4/okhttp-3.14.4.jar
Action:
Correct the classpath of your application so that it contains a single, compatible version of okhttp3.RequestBody
进程已结束,退出代码为 1
从 io.minio.S3Base.(S3Base.java:104) 这一行点进去可以看到错误原因。
static {
try {
RequestBody.create(new byte[] {}, null);
} catch (NoSuchMethodError ex) {
throw new RuntimeException("Unsupported OkHttp library found. Must use okhttp >= 4.8.1", ex);
}
}
pom minio 依赖点进去可以找到 okhttp的依赖
这里已经定义了 4.10.0版本,为什么到了外面不生效呢 ?
我们找到 spring-boot-starter-parent 依赖搜索 okhttp
发现没有结果,于是进入到 spring-boot-dependencies 中搜索 okhttp
我们来约束一下版本
<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.squareup.okhttp3groupId>
<artifactId>okhttpartifactId>
<version>4.10.0version>
dependency>
dependencies>
dependencyManagement>
<dependency>
<groupId>io.miniogroupId>
<artifactId>minioartifactId>
<version>8.4.5version>
<exclusions>
<exclusion>
<groupId>com.squareup.okhttp3groupId>
<artifactId>okhttpartifactId>
exclusion>
exclusions>
dependency>
<dependency>
<groupId>com.squareup.okhttp3groupId>
<artifactId>okhttpartifactId>
<version>4.10.0version>
dependency>
在 yml 定义几个配置
minio:
enable: true
endpoint: 127.0.0.1:9000
accessKey: account
secretKey: password
bucketName: mkdir1
定义一个config来读取yml配置
@Configuration
public class MinIoClientConfig {
private final static Logger logger = LoggerFactory.getLogger(MinIoClientConfig.class);
@Value("${minio.endpoint}")
private String endpoint;
@Value("${minio.accessKey}")
private String accessKey;
@Value("${minio.secretKey}")
private String secretKey;
@Value("${minio.enable}")
private boolean enable;
@Value("${minio.bucketName}")
private String bucketName;
@Bean
public MinioClient minioClient() {
if (enable) {
MinioClient client = MinioClient.builder()
.endpoint(endpoint)
.credentials(accessKey, secretKey)
.build();
//尝试链接测试
try {
List<Bucket> buckets = client.listBuckets();
List<String> bucketNames = buckets.stream().map(Bucket::name).collect(Collectors.toList());
boolean contains = bucketNames.contains(bucketName);
if (!contains) {
logger.error("minio Client Bucket not contains " + bucketName
+ ", Client build ERROR, Please Check your bucketName");
return null;
}
logger.info("minio client build SUCCESS");
return client;
} catch (Exception e) {
logger.error("minio client error {}", e.getMessage());
return null;
}
}
return null;
}
// 老版本
// @Bean
// public MinioClient minioClient() throws Exception {
// if (enable) {
// MinioClient client = new MinioClient(endpoint, accessKey, secretKey);
// logger.info("minio client build success ... ... ... ");
// return client;
// }
// return null;
// }
public String getEndpoint() {
return endpoint;
}
public void setEndpoint(String endpoint) {
this.endpoint = endpoint;
}
public String getAccessKey() {
return accessKey;
}
public void setAccessKey(String accessKey) {
this.accessKey = accessKey;
}
public String getSecretKey() {
return secretKey;
}
public void setSecretKey(String secretKey) {
this.secretKey = secretKey;
}
public boolean isEnable() {
return enable;
}
public void setEnable(boolean enable) {
this.enable = enable;
}
public String getBucketName() {
return bucketName;
}
public void setBucketName(String bucketName) {
this.bucketName = bucketName;
}
}
创建bean的时候引入连接测试获取 listBuckets ,如果获取失败则提示如下效果。
定义上传文件工具类
@Service
public class MinioUtils {
private final static Logger logger = LoggerFactory.getLogger(MinioUtils.class);
@Autowired
MinIoClientConfig clientConfig;
@Autowired(required = false)
MinioClient client;
public String upload(MultipartFile file) throws Exception {
if (client == null) {
throw new RuntimeException("minio enable is false or connection failure ");
}
String saveFileName = preUpload(file, null);
String bucketName = clientConfig.getBucketName();
//判断桶是否存在
BucketExistsArgs args = BucketExistsArgs.builder().bucket(bucketName).build();
boolean exists = client.bucketExists(args);
if (!exists) {
//创建存储桶
MakeBucketArgs makeBucketArgs = MakeBucketArgs.builder().bucket(bucketName).build();
client.makeBucket(makeBucketArgs);
//配置权限 需要手动将桶设置为公共读
// SetBucketPolicyArgs policy = SetBucketPolicyArgs.builder().bucket(bucketName).config("null").build();
// client.setBucketPolicy(policy);
}
//上传文件
PutObjectArgs put = PutObjectArgs.builder()
.stream(file.getInputStream(), file.getSize(), -1)
.bucket(bucketName).object(saveFileName).build();
ObjectWriteResponse response = client.putObject(put);
//
GetObjectArgs get = GetObjectArgs.builder().bucket(bucketName).object(saveFileName).build();
GetObjectResponse getResponse = client.getObject(get);
//
GetPresignedObjectUrlArgs urlArg = GetPresignedObjectUrlArgs.builder()
.bucket(bucketName).object(saveFileName).method(Method.GET).build();
return client.getPresignedObjectUrl(urlArg).split("\\?")[0];
}
// 7.0 版本
// public String upload(MultipartFile file, String saveFileName) throws Exception {
// if (client == null) {
// throw new RuntimeException("minio enable is false ");
// }
// String bucketName = clientConfig.getBucketName();
// //判断桶是否存在
// boolean exists = client.bucketExists(bucketName);
// if (!exists) {
// throw new RuntimeException("minio bucket " + bucketName + " need create ");
// }
// //上传文件
// PutObjectOptions options = new PutObjectOptions(file.getSize(), -1);
// client.putObject(bucketName, saveFileName, file.getInputStream(), options);
// //获取返回url
// String url = client.presignedPutObject(bucketName, saveFileName).split("\\?")[0];
// logger.info("upload success url is {}", url);
// return url;
// }
/**
* 处理路径和名字
*
* @param file
* @return
*/
public String preUpload(MultipartFile file, String mkdir) {
String fileAllName = file.getOriginalFilename();
String prefix = LocalDateTime.now().toString().replaceAll(":", "-");
String suffix = fileAllName.substring(fileAllName.lastIndexOf("."));
if (mkdir == null) {
return prefix + suffix;
}
return mkdir + "/" + prefix + suffix;
}
}
根据业务扩展即可
定义一个测试controller
@RestController
@RequestMapping("file")
public class FileController {
@Autowired
MinioUtils service;
@PostMapping(value = "upload", headers = "content-type=multipart/form-data")
public String upload(@RequestPart("file") MultipartFile file) throws Exception {
return service.upload(file);
}
}
上传效果图如下
关于 Buckets 需要手动将权限改为 public
如果是 private 生成的 url 会带有默认的 7天时间,超过会过期。
如果是 private 权限,生成的 url 就不要做 split 截取,否则可能会无法访问
docker run -d -p 9000:9000 -p 9001:9001 --name minio1 \
-e "MINIO_ACCESS_KEY=zhanghao" \
-e "MINIO_SECRET_KEY=password" \
-v /mnt/data:/data \
-v /mnt/config:/root/.minio \
minio/minio server /data
9000是API地址,9001是控制台地址。