├── .gitignore
├── LICENSE
├── README.md
├── pom.xml
└── src
├── main
├── java
│ └── me
│ │ └── tuine
│ │ └── minio
│ │ ├── MinioApplication.java
│ │ ├── configurer
│ │ ├── MinIoUtils.java
│ │ └── MinioProperties.java
│ │ ├── controller
│ │ └── IndexController.java
│ │ ├── service
│ │ ├── UploadService.java
│ │ └── impl
│ │ │ └── UploadServiceImpl.java
│ │ └── util
│ │ └── CustomMinioClient.java
└── resources
│ └── application.yml
└── test
└── java
└── me
└── tuine
└── minio
└── MinioApplicationTests.java
/.gitignore:
--------------------------------------------------------------------------------
1 | HELP.md
2 | target/
3 | !.mvn/wrapper/maven-wrapper.jar
4 | !**/src/main/**/target/
5 | !**/src/test/**/target/
6 |
7 | ### STS ###
8 | .apt_generated
9 | .classpath
10 | .factorypath
11 | .project
12 | .settings
13 | .springBeans
14 | .sts4-cache
15 |
16 | ### IntelliJ IDEA ###
17 | .idea
18 | *.iws
19 | *.iml
20 | *.ipr
21 |
22 | ### NetBeans ###
23 | /nbproject/private/
24 | /nbbuild/
25 | /dist/
26 | /nbdist/
27 | /.nb-gradle/
28 | build/
29 | !**/src/main/**/build/
30 | !**/src/test/**/build/
31 |
32 | ### VS Code ###
33 | .vscode/
34 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2021 tuine
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Quickstart Guide
2 | ## Introduction
3 | Multipart objects using presigned URLs, upload directly to the server without transiting through the business server.
4 |
5 | This is just a simple demo, please improve the code according to actual business.
6 |
7 | ## Integration example
8 | curl example:
9 | 1. Init multipart upload
10 | ```shell script
11 | $ curl --location --request POST '127.0.0.1:8006/multipart/init' \
12 | --header 'Content-Type: application/json' \
13 | --data-raw '{
14 | "filename": "b.jpg",
15 | "partCount": 2,
16 | "contentType": "image/jpeg"
17 | }'
18 | ```
19 | Response example:
20 | ```js
21 | {
22 | "uploadId": "b7dd9a60-7c11-43f1-acee-bffd4ef2fccb",
23 | "uploadUrls": [
24 | "https://play.minio.io:9000/tuinetest/test/b.jpg?uploadId=b7dd9a60-7c11-43f1-acee-bffd4ef2fccb&partNumber=1&X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=Q3AM3UQ867SPQQA43P2F%2F20210324%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20210324T032112Z&X-Amz-Expires=86400&X-Amz-SignedHeaders=host&X-Amz-Signature=e39c8e8c165add0daa50d2da44e51ca752b9213e497633bcfb3431b60383b5be",
25 | "https://play.minio.io:9000/tuinetest/test/b.jpg?uploadId=b7dd9a60-7c11-43f1-acee-bffd4ef2fccb&partNumber=2&X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=Q3AM3UQ867SPQQA43P2F%2F20210324%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20210324T032112Z&X-Amz-Expires=86400&X-Amz-SignedHeaders=host&X-Amz-Signature=99611a212d6b791a24df295cb3475a79780ed4c6314ee9ddb8df4179326b7723"
26 | ]
27 | }
28 | ```
29 |
30 | 2. Uploading objects using presigned URLs
31 |
32 | Cut the picture into two parts
33 | ```shell script
34 | curl --location --request PUT 'https://play.minio.io:9000/tuinetest/test/b.jpg?uploadId=b7dd9a60-7c11-43f1-acee-bffd4ef2fccb&partNumber=1&X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=Q3AM3UQ867SPQQA43P2F%2F20210324%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20210324T032112Z&X-Amz-Expires=86400&X-Amz-SignedHeaders=host&X-Amz-Signature=e39c8e8c165add0daa50d2da44e51ca752b9213e497633bcfb3431b60383b5be' \
35 | --header 'Content-Type: application/octet-stream' \
36 | --data-binary '@/D:/jpgone'
37 | curl --location --request PUT 'https://play.minio.io:9000/tuinetest/test/b.jpg?uploadId=b7dd9a60-7c11-43f1-acee-bffd4ef2fccb&partNumber=2&X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=Q3AM3UQ867SPQQA43P2F%2F20210324%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20210324T032112Z&X-Amz-Expires=86400&X-Amz-SignedHeaders=host&X-Amz-Signature=99611a212d6b791a24df295cb3475a79780ed4c6314ee9ddb8df4179326b7723' \
38 | --header 'Content-Type: application/octet-stream' \
39 | --data-binary '@/D:/jpgtwo'
40 | ```
41 |
42 | 3. Complete
43 | ```shell script
44 | curl --location --request PUT '127.0.0.1:8006/multipart/complete' \
45 | --header 'Content-Type: application/json' \
46 | --data-raw '{
47 | "objectName":"test/b.jpg",
48 | "uploadId":"b7dd9a60-7c11-43f1-acee-bffd4ef2fccb"
49 | }'
50 | ```
51 |
52 | ## Verify upload
53 |
54 | Login Minio: [play MinIo](https://play.minio.io:9000/minio/tuinetest/)
55 | Username: Q3AM3UQ867SPQQA43P2F
56 | Password: zuf+tfteSlswRu7BJ86wekitnifILbZam1KYY3TG
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 | 4.0.0
5 |
6 | org.springframework.boot
7 | spring-boot-starter-parent
8 | 2.2.6.RELEASE
9 |
10 |
11 | me.tuine
12 | miniodemo
13 | 0.0.1-SNAPSHOT
14 | 分片上传示例
15 | minio multipart upload demo
16 |
17 | 11
18 |
19 |
20 |
21 | org.springframework.boot
22 | spring-boot-starter-web
23 |
24 |
25 |
26 | org.springframework.boot
27 | spring-boot-starter-test
28 | test
29 |
30 |
31 | cn.hutool
32 | hutool-all
33 | 5.4.1
34 |
35 |
36 | org.projectlombok
37 | lombok
38 | 1.18.4
39 | true
40 |
41 |
42 | io.minio
43 | minio
44 | 8.0.3
45 |
46 |
47 |
48 |
49 |
50 |
51 | org.springframework.boot
52 | spring-boot-maven-plugin
53 |
54 |
55 |
56 |
57 |
58 |
--------------------------------------------------------------------------------
/src/main/java/me/tuine/minio/MinioApplication.java:
--------------------------------------------------------------------------------
1 | package me.tuine.minio;
2 |
3 | import org.springframework.boot.SpringApplication;
4 | import org.springframework.boot.autoconfigure.SpringBootApplication;
5 |
6 | @SpringBootApplication
7 | public class MinioApplication {
8 |
9 | public static void main(String[] args) {
10 | SpringApplication.run(MinioApplication.class, args);
11 | }
12 |
13 | }
14 |
--------------------------------------------------------------------------------
/src/main/java/me/tuine/minio/configurer/MinIoUtils.java:
--------------------------------------------------------------------------------
1 | package me.tuine.minio.configurer;
2 |
3 | import cn.hutool.core.util.StrUtil;
4 | import com.google.common.collect.HashMultimap;
5 | import io.minio.http.Method;
6 | import io.minio.messages.Part;
7 | import me.tuine.minio.util.CustomMinioClient;
8 | import org.springframework.boot.context.properties.EnableConfigurationProperties;
9 | import org.springframework.context.annotation.Configuration;
10 | import org.springframework.stereotype.Component;
11 | import io.minio.*;
12 |
13 | import javax.annotation.PostConstruct;
14 | import java.util.ArrayList;
15 | import java.util.HashMap;
16 | import java.util.List;
17 | import java.util.Map;
18 | import java.util.concurrent.TimeUnit;
19 |
20 | /**
21 | * @author tuine
22 | * @date 2021/3/23
23 | */
24 | @Component
25 | @Configuration
26 | @EnableConfigurationProperties({MinioProperties.class})
27 | public class MinIoUtils {
28 | private final MinioProperties minioProperties;
29 | private CustomMinioClient customMinioClient;
30 |
31 | public MinIoUtils(MinioProperties minioProperties) {
32 | this.minioProperties = minioProperties;
33 | }
34 |
35 | @PostConstruct
36 | public void init() {
37 | MinioClient minioClient = MinioClient.builder()
38 | .endpoint(minioProperties.getEndpoint())
39 | .credentials(minioProperties.getAccesskey(), minioProperties.getSecretkey())
40 | .build();
41 | customMinioClient = new CustomMinioClient(minioClient);
42 | }
43 |
44 | /**
45 | * 单文件签名上传
46 | *
47 | * @param objectName 文件全路径名称
48 | * @return /
49 | */
50 | public String getUploadObjectUrl(String objectName) {
51 | // 上传文件时携带content-type头即可
52 | /*if (StrUtil.isBlank(contentType)) {
53 | contentType = "application/octet-stream";
54 | }
55 | HashMultimap headers = HashMultimap.create();
56 | headers.put("Content-Type", contentType);*/
57 | try {
58 | return customMinioClient.getPresignedObjectUrl(
59 | GetPresignedObjectUrlArgs.builder()
60 | .method(Method.PUT)
61 | .bucket(minioProperties.getBucket())
62 | .object(objectName)
63 | .expiry(1, TimeUnit.DAYS)
64 | //.extraHeaders(headers)
65 | .build()
66 | );
67 | } catch (Exception e) {
68 | e.printStackTrace();
69 | return null;
70 | }
71 | }
72 |
73 | /**
74 | * 初始化分片上传
75 | *
76 | * @param objectName 文件全路径名称
77 | * @param partCount 分片数量
78 | * @param contentType 类型,如果类型使用默认流会导致无法预览
79 | * @return /
80 | */
81 | public Map initMultiPartUpload(String objectName, int partCount, String contentType) {
82 | Map result = new HashMap<>();
83 | try {
84 | if (StrUtil.isBlank(contentType)) {
85 | contentType = "application/octet-stream";
86 | }
87 | HashMultimap headers = HashMultimap.create();
88 | headers.put("Content-Type", contentType);
89 | String uploadId = customMinioClient.initMultiPartUpload(minioProperties.getBucket(), null, objectName, headers, null);
90 |
91 | result.put("uploadId", uploadId);
92 | List partList = new ArrayList<>();
93 |
94 | Map reqParams = new HashMap<>();
95 | //reqParams.put("response-content-type", "application/json");
96 | reqParams.put("uploadId", uploadId);
97 | for (int i = 1; i <= partCount; i++) {
98 | reqParams.put("partNumber", String.valueOf(i));
99 | String uploadUrl = customMinioClient.getPresignedObjectUrl(
100 | GetPresignedObjectUrlArgs.builder()
101 | .method(Method.PUT)
102 | .bucket(minioProperties.getBucket())
103 | .object(objectName)
104 | .expiry(1, TimeUnit.DAYS)
105 | .extraQueryParams(reqParams)
106 | .build());
107 | partList.add(uploadUrl);
108 | }
109 | result.put("uploadUrls", partList);
110 | } catch (Exception e) {
111 | e.printStackTrace();
112 | return null;
113 | }
114 |
115 | return result;
116 | }
117 |
118 | /**
119 | * 分片上传完后合并
120 | *
121 | * @param objectName 文件全路径名称
122 | * @param uploadId 返回的uploadId
123 | * @return /
124 | */
125 | public boolean mergeMultipartUpload(String objectName, String uploadId) {
126 | try {
127 | //TODO::目前仅做了最大1000分片
128 | Part[] parts = new Part[1000];
129 | ListPartsResponse partResult = customMinioClient.listMultipart(minioProperties.getBucket(), null, objectName, 1000, 0, uploadId, null, null);
130 | int partNumber = 1;
131 | for (Part part : partResult.result().partList()) {
132 | parts[partNumber - 1] = new Part(partNumber, part.etag());
133 | partNumber++;
134 | }
135 | customMinioClient.mergeMultipartUpload(minioProperties.getBucket(), null, objectName, uploadId, parts, null, null);
136 | } catch (Exception e) {
137 | e.printStackTrace();
138 | return false;
139 | }
140 |
141 | return true;
142 | }
143 | }
144 |
--------------------------------------------------------------------------------
/src/main/java/me/tuine/minio/configurer/MinioProperties.java:
--------------------------------------------------------------------------------
1 | package me.tuine.minio.configurer;
2 |
3 | import lombok.Getter;
4 | import lombok.Setter;
5 | import org.springframework.boot.context.properties.ConfigurationProperties;
6 |
7 | /**
8 | * @author tuine
9 | * @date 2021/3/23
10 | */
11 | @ConfigurationProperties(prefix = "minio")
12 | @Getter
13 | @Setter
14 | public class MinioProperties {
15 |
16 | private String endpoint;
17 |
18 | private String accesskey;
19 |
20 | private String secretkey;
21 |
22 | private String bucket;
23 | }
24 |
--------------------------------------------------------------------------------
/src/main/java/me/tuine/minio/controller/IndexController.java:
--------------------------------------------------------------------------------
1 | package me.tuine.minio.controller;
2 |
3 | import cn.hutool.core.lang.Assert;
4 | import cn.hutool.json.JSONObject;
5 | import com.google.common.collect.ImmutableMap;
6 | import lombok.RequiredArgsConstructor;
7 | import me.tuine.minio.service.UploadService;
8 | import org.springframework.http.HttpStatus;
9 | import org.springframework.http.ResponseEntity;
10 | import org.springframework.web.bind.annotation.*;
11 |
12 | import java.util.Map;
13 |
14 | /**
15 | * @author tuine
16 | * @date 2021/3/23
17 | */
18 | @RestController
19 | @RequiredArgsConstructor
20 | public class IndexController {
21 |
22 | private final UploadService uploadService;
23 |
24 | /**
25 | * 分片初始化
26 | *
27 | * @param requestParam 请求参数-此处简单处理
28 | * @return /
29 | */
30 | @PostMapping("/multipart/init")
31 | public ResponseEntity