This article has participated in the “Digitalstar Project” and won a creative gift package to challenge the creative incentive money.

In this article we used SpringBoot to integrate miniO. We did not use its starter directly here because there are only two versions in the Maven repository and they are not widely used. Here we can write our own starter and refer to other projects directly.

First of all, the Chinese document version of Minio does not match the latest version at all, and I cannot access the English official website all the time. I wonder if you have the same problem.

Refer to my previous article on building Minio: juejin.cn/post/701800…

Without further ado, let’s get to the point.

I. POM dependence

The version I used:

<! -- https://mvnrepository.com/artifact/io.minio/minio --> <dependency> <groupId>io.minio</groupId> < artifactId > minio < / artifactId > < version > 8.2.1 < / version > < / dependency >Copy the code

I am using the latest version 8.3.0, but when all the code is finished, I find that the startup error:

*************************** 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.<clinit>(S3Base.java:105) 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: / apache maven - 3.6.3 / repo/com/squareup okhttp3 / okhttp 3.14.9 / okhttp - 3.14.9. Jar! /okhttp3/RequestBody.class It was loaded from the following location: File: / D: / apache maven - 3.6.3 / repo/com/squareup okhttp3 / okhttp 3.14.9 / okhttp - 3.14.9. Jar Action: Correct the classpath of your application so that it contains a single, Compatible Version of OKHttp3. RequestBody 2021-08-25 13:01:29.975 [graph-Editor: N/A] [ERROR] com.vtc.core.analysis.Slf4jFailureAnalysisReporter - *************************** 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.<clinit>(S3Base.java:105) 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: / apache maven - 3.6.3 / repo/com/squareup okhttp3 / okhttp 3.14.9 / okhttp - 3.14.9. Jar! /okhttp3/RequestBody.class It was loaded from the following location: File: / D: / apache maven - 3.6.3 / repo/com/squareup okhttp3 / okhttp 3.14.9 / okhttp - 3.14.9. Jar Action: Correct the classpath of your application so that it contains a single, compatible version of okhttp3.RequestBodyCopy the code

I thought there was a problem with the version of OKHTTP or the duplicate package, but it didn’t work. The final solution was to reduce the version of Minio to 8.2.1, and you can try to reduce the version.

Configuration file

We need to prepare the following contents: miniO service address, user name, password, bucket name in the configuration file YAML:

Minio: endpoint: http://172.16.3.28:10000 accessKey: admin secretKey: 12345678 bucketName: AAACopy the code

In addition, set spring’s maximum upload file limit. If this still does not work, please consider whether it is a gateway or nginx still needs to be configured. In the final configuration file, I give the size of 100m:

Servlet: multipart: max-file-size: 100MB max-request-size: 100MBCopy the code

3. Configuration classes

Here you need two configuration classes, one for property configuration, to read the yamL configuration; Initialize MinioClient into the Spring container:

import lombok.Data; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.stereotype.Component; /** * description: minio configuration class ** @author: weirx * @time: 2021/8/25 9:47 */ @Data @Component @ConfigurationProperties(prefix = "minio") public class MinioPropertiesConfig { /** * Endpoint */ private String endpoint; /** * user name */ private String accessKey; /** * private String secretKey; /** * private String bucketName; }Copy the code
import io.minio.MinioClient; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import javax.annotation.Resource; ** @author: weirx * @time: 2021/8/25 9:50 */ @Configuration @EnableConfigurationProperties(MinioPropertiesConfig.class) public class MinioConfig { @Resource private MinioPropertiesConfig minioPropertiesConfig; / / @bean public MinioClient MinioClient () {MinioClient MinioClient = minioclient.builder () .endpoint(minioPropertiesConfig.getEndpoint()) .credentials(minioPropertiesConfig.getAccessKey(), minioPropertiesConfig.getSecretKey()) .build(); return minioClient; }}Copy the code

4. Tool classes

Provide a simple utility class for other services to call directly, including upload, download:

import com.baomidou.mybatisplus.core.toolkit.Constants; import com.vtc.core.utils.DownLoadUtils; import io.minio.*; import org.apache.commons.io.IOUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Component; import org.springframework.web.multipart.MultipartFile; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.UnsupportedEncodingException; import java.net.URLEncoder; import java.util.ArrayList; import java.util.Arrays; import java.util.List; /** * @description: minio class * @author: weirx * @date: 2021/8/25 10:03 * @version: 3.0 */ @Component Public class MinioUtil {@value ("${minio.bucketName}") private String bucketName; @Autowired private MinioClient minioClient; ** @return: void * @author: weirx * @time: 2021/8/25 10:20 */ public void existBucket(String name) { try { boolean exists = minioClient.bucketExists(BucketExistsArgs.builder().bucket(name).build()); if (! exists) { minioClient.makeBucket(MakeBucketArgs.builder().bucket(name).build()); } } catch (Exception e) { e.printStackTrace(); }} /** * description: Upload file ** @param multipartFile * @return: java.lang.String * @author: weirx * @time: 2021/8/25 10:44 */ public List<String> upload(MultipartFile[] multipartFile) { List<String> names = new ArrayList<>(multipartFile.length); for (MultipartFile file : multipartFile) { String fileName = file.getOriginalFilename(); String[] split = fileName.split("\\."); if (split.length > 1) { fileName = split[0] + "_" + System.currentTimeMillis() + "." + split[1]; } else { fileName = fileName + System.currentTimeMillis(); } InputStream in = null; try { in = file.getInputStream(); minioClient.putObject(PutObjectArgs.builder() .bucket(bucketName) .object(fileName) .stream(in, in.available(), -1) .contentType(file.getContentType()) .build() ); } catch (Exception e) { e.printStackTrace(); } finally { if (in ! = null) { try { in.close(); } catch (IOException e) { e.printStackTrace(); } } } names.add(fileName); } return names; } / * * * description: download file * * * @ @ param fileName return: org. Springframework. HTTP. ResponseEntity < byte [] > * @ the author: weirx * @time: 2021/8/25 10:34 */ public ResponseEntity<byte[]> download(String fileName) { ResponseEntity<byte[]> responseEntity = null; InputStream in = null; ByteArrayOutputStream out = null; try { in = minioClient.getObject(GetObjectArgs.builder().bucket(bucketName).object(fileName).build()); out = new ByteArrayOutputStream(); IOUtils.copy(in, out); Byte [] bytes = out.tobytearray (); byte[] bytes = out.tobytearray (); HttpHeaders headers = new HttpHeaders(); try { headers.add("Content-Disposition", "attachment; filename=" + URLEncoder.encode(fileName, Constants.UTF_8)); } catch (UnsupportedEncodingException e) { e.printStackTrace(); } headers.setContentLength(bytes.length); headers.setContentType(MediaType.APPLICATION_OCTET_STREAM); headers.setAccessControlExposeHeaders(Arrays.asList("*")); responseEntity = new ResponseEntity<byte[]>(bytes, headers, HttpStatus.OK); } catch (Exception e) { e.printStackTrace(); } finally { try { if (in ! = null) { try { in.close(); } catch (IOException e) { e.printStackTrace(); } } if (out ! = null) { out.close(); } } catch (IOException e) { e.printStackTrace(); } } return responseEntity; }}Copy the code

If other projects want to use it, you can change the ResponseBody and download it directly. Here mainly refer to how to use MinioClient upload, download the file is good.

Five, test a wave

We use SpringBoot to integrate knife4J and access the interface documentation directly through the gateway, as does Postman. I offer the following simple interfaces to test it out.

@apiOperation (value = "minio upload test ") @postmapping ("/upload") public List<String> upload(@requestParam (name = "multipartFile") MultipartFile[] multipartFile) { return minioUtil.upload(multipartFile); } @apiOperation (value = "miniO download test ") @getMapping ("/download") public ResponseEntity<byte[]> Download (@requestParam) String fileName) { return minioUtil.download(fileName); } @apiOperation (value = "minio create bucket ") @postMapping ("/existBucket") public void existBucket(@requestParam String) bucketName) { minioUtil.existBucket(bucketName); }Copy the code

Interface page upload document look at:

A pit came, found the return successful, file name. But there is no data in the minio console?

A look at the background error, a long piece:

error occurred
ErrorResponse(code = SignatureDoesNotMatch, message = The request signature we calculated does not match the signature you provided. Check your key and signing method., bucketName = esmp, objectName = null, resource = /esmp, requestId = 169E753DE01FE2AF, hostId = 29aa9dc9-661b-432e-a25f-9856ad3a8250)
request={method=GET, url=http://172.16.3.28:10000/esmp?location=, headers=Host: 172.16.3.28:10000
Accept-Encoding: identity
User-Agent: MinIO (Windows 10; amd64) minio-java/8.2.1
Content-MD5: 1B2M2Y8AsgTpgAmY7PhCfg==
x-amz-content-sha256: e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
x-amz-date: 20210825T052344Z
Authorization: AWS4-HMAC-SHA256 Credential=*REDACTED*/20210825/us-east-1/s3/aws4_request, SignedHeaders=content-md5;host;x-amz-content-sha256;x-amz-date, Signature=*REDACTED*
}
response={code=403, headers=Server: nginx/1.20.1
Date: Wed, 25 Aug 2021 05:23:43 GMT
Content-Type: application/xml
Content-Length: 367
Connection: keep-alive
Accept-Ranges: bytes
Content-Security-Policy: block-all-mixed-content
Strict-Transport-Security: max-age=31536000; includeSubDomains
Vary: Origin
Vary: Accept-Encoding
X-Amz-Request-Id: 169E753DE01FE2AF
X-Content-Type-Options: nosniff
X-Xss-Protection: 1; mode=block
}

	at io.minio.S3Base.execute(S3Base.java:667)
	at io.minio.S3Base.getRegion(S3Base.java:691)
	at io.minio.S3Base.putObject(S3Base.java:2003)
	at io.minio.S3Base.putObject(S3Base.java:1153)
	at io.minio.MinioClient.putObject(MinioClient.java:1666)
	at com.vtc.minio.util.MinioUtil.upload(MinioUtil.java:72)
	at com.mvtech.graph.ui.GraphCanvasUI.upload(GraphCanvasUI.java:84)
	at com.mvtech.graph.ui.GraphCanvasUI$$FastClassBySpringCGLIB$$5138ff62.invoke(<generated>)
	at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:218)
	at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:771)
	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163)
	at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:749)
	at com.baidu.unbiz.fluentvalidator.interceptor.FluentValidateInterceptor.invoke(FluentValidateInterceptor.java:211)
	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:175)
	at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:749)
	at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:95)
	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
	at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:749)
	at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:691)
	at com.mvtech.graph.ui.GraphCanvasUI$$EnhancerBySpringCGLIB$$e773947f.upload(<generated>)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:498)
	at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:190)
	at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:138)
	at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:105)
	at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:878)
	at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:792)
	at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87)
	at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1040)
	at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:943)
	at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1006)
	at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:909)
	at javax.servlet.http.HttpServlet.service(HttpServlet.java:665)
	at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:883)
	at javax.servlet.http.HttpServlet.service(HttpServlet.java:750)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:231)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
	at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
	at com.github.xiaoymin.knife4j.spring.filter.ProductionSecurityFilter.doFilter(ProductionSecurityFilter.java:53)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
	at com.github.xiaoymin.knife4j.spring.filter.SecurityBasicAuthFilter.doFilter(SecurityBasicAuthFilter.java:90)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
	at org.springframework.web.filter.CorsFilter.doFilterInternal(CorsFilter.java:92)
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:113)
	at com.botany.spore.core.page.PageRequestFilter.doFilterInternal(PageRequestFilter.java:92)
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
	at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100)
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
	at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93)
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
	at org.springframework.boot.actuate.metrics.web.servlet.WebMvcMetricsFilter.doFilterInternal(WebMvcMetricsFilter.java:109)
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
	at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201)
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
	at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:202)
	at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:97)
	at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:541)
	at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:143)
	at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92)
	at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:78)
	at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:343)
	at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:374)
	at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:65)
	at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:868)
	at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1590)
	at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
	at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
	at java.lang.Thread.run(Thread.java:748)
Copy the code

What’s the reason? Because my minio is in cluster mode, I use Nginx load, this error is reported, about the incorrect Nginx configuration and how to set up the environment is mentioned in the first article of my article.

Change the load port from 10000 to 9000, then everything is ok, no matter upload or download:

Minio: endpoint: http://172.16.3.28:9000 accessKey: admin secretKey: 12345678 bucketName: AAACopy the code

How to solve the nginx load problem? This problem is related to the header carried by the nginx reverse proxy when forwarding. Minio must obtain the host from the HTTP header to verify whether the signature is valid, and we do not do the necessary processing for the header. So we need to add the following configuration:

proxy_set_header Host $http_host;
Copy the code

The complete nginx configuration is as follows:

# For more information on configuration, see: # * Official English Documentation: http://nginx.org/en/docs/ # * Official Russian Documentation: http://nginx.org/ru/docs/ user nginx; worker_processes auto; error_log /var/log/nginx/error.log; pid /run/nginx.pid; # Load dynamic modules. See /usr/share/doc/nginx/README.dynamic. include /usr/share/nginx/modules/*.conf; events { worker_connections 1024; } http { log_format main '$remote_addr - $remote_user [$time_local] "$request" ' '$status $body_bytes_sent "$http_referer" ' '"$http_user_agent" "$http_x_forwarded_for"'; access_log /var/log/nginx/access.log main; sendfile on; tcp_nopush on; tcp_nodelay on; keepalive_timeout 65; types_hash_max_size 4096; include /etc/nginx/mime.types; default_type application/octet-stream; # Load modular configuration files from the /etc/nginx/conf.d directory. # See http://nginx.org/en/docs/ngx_core_module.html#include # for more information. include /etc/nginx/conf.d/*.conf; Upstream minio {server 172.16.3.28:9000 fail_timeout=10s max_fails=2 weight=1; Server 172.16.3.29:9000 Fail_timeout =10s max_fails=2 weight=1; Server 172.16.3.30:9000 Fail_timeout =10s max_fails=2 weight=1; } upstream minio-console {server 172.16.3.28:10001 fail_timeout=10s max_fails=2 weight=1; Server 172.16.3.29:10001 Fail_timeout =10s max_fails=2 weight=1; Server 172.16.3.30:10001 Fail_timeout =10s max_fails=2 weight=1; } server { listen 10000; root /usr/share/nginx/html; client_max_body_size 100m; # Load configuration files for the default server block. include /etc/nginx/default.d/*.conf; location / { proxy_pass http://minio; 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 $remote_addr; proxy_set_header Host $http_host; } error_page 404 /404.html; location = /404.html { } error_page 500 502 503 504 /50x.html; location = /50x.html { } } server { listen 11000; root /usr/share/nginx/html; # Load configuration files for the default server block. include /etc/nginx/default.d/*.conf; location / { proxy_pass http://minio-console; 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 $remote_addr; proxy_set_header Host $http_host; } error_page 404 /404.html; location = /404.html { } error_page 500 502 503 504 /50x.html; location = /50x.html { } } }Copy the code

Upload test again, successful:


That’s it! Don’t forget to configure Spring.Factories if you need to be a starter.