RU: Spring Boot - File Upload, Download - via Minio or AWS S3 SDK

Цель:
1. Пример работы Spring Boot и загрузки файла на сервер
2. Использовать MinIO клиент и загрузка фалов через него
3. Использовать AWS S3 SDK клиент и загрузка файлов на MinIO сервер(AWS S3 compatible)
Что нам потребуется создать spring boot проект, для этого обратимся к spring initialzr-у https://start.spring.io/
Создадим проект с минимальным набором копонентов - прежде всего это Spring Web собственно и всё остальное нам знать не обязательно
Мы создадим основные методы по загрузки файлов на minio хранилище
скачиванию файлов, удалению их по ключу(имени) и просмотр списка файлов в бакете. Бакет в рамках AWS S3 называется главная директория, в которой мы сохраняем свои файлы - например, мы можем разделять набор таких бакетов для video, photo, documents, - определяя при загрузке, где что будет хранится и какие типы, но это больше частное архитектурное решение и к примеру не относится.
Для работы с хранилищем через minio client-а или Amazon S3 Java SDK нам потребуется указать в pom.xml соответсвующие зависимости:
<!-- ... -->
<!-- minio client -->
<dependency>
<groupId>io.minio</groupId>
<artifactId>minio</artifactId>
<version>7.0.2</version>
</dependency>
<!-- aws java sdk client -->
<dependency>
<groupId>com.amazonaws</groupId>
<artifactId>aws-java-sdk</artifactId>
<version>${aws.sdk.version}</version>
</dependency>
<!-- ioutils будем использовать из этой библиотеки для простоты -->
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.3.1</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-io</artifactId>
<version>1.3.2</version>
</dependency>
...
Итак для работы с хранилищем MinIO нам потребуются два ключевых параметра - это ACCESS KEY и SECRET KEY, рекомендуется задать их в переменных окружения, для нашего спринг бут приложения зададим их в конфиг файле application.properties
s3.url=https://play.min.io
s3.accessKey=Q3AM3UQ867SPQQA43P2F
s3.secretKey=zuf+tfteSlswRu7BJ86wekitnifILbZam1KYY3TG
spring.servlet.multipart.max-file-size=100MB
spring.servlet.multipart.max-request-size=100MB
Как можно заметить тут указаны еще два параметра необходимых для загрузки файлов, которые повышают стандартный размер файла до 100 mb, c 10 mb default(по умолчанию)
Иии ... вопрос, а причем тут параметр??? s3.url=
https://play.min.io
- верный вопрос. Для простоты примера, мы используем онлайн сервер minio хранилища, который открыт бесплатно для тестирования API. Так что нам даже не обязательно его устанавливать, разве это не прекрасно?!😂
Отлично, что дальше - создаем свой конфигурационный файл, где указываем клиента AmazonS3 или MinioClient - бины для работы с хранилищами. В нашем примере укажем оба, но при реализации контроллера остановимся на одном конечном AmazonS3 👇👇👇
package com.timurisachenko.microstorage.configs;
import com.amazonaws.ClientConfiguration;
import com.amazonaws.auth.AWSCredentials;
import com.amazonaws.auth.AWSStaticCredentialsProvider;
import com.amazonaws.auth.BasicAWSCredentials;
import com.amazonaws.client.builder.AwsClientBuilder;
import com.amazonaws.regions.Regions;
import com.amazonaws.services.s3.AmazonS3;
import com.amazonaws.services.s3.AmazonS3ClientBuilder;
import io.minio.MinioClient;
import io.minio.errors.InvalidEndpointException;
import io.minio.errors.InvalidPortException;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class AppConfig {
@Value("${s3.url}")
private String s3Url;
@Value("${s3.accessKey}")
private String accessKey;
@Value("${s3.secretKey}")
private String secretKey;
@Bean
public MinioClient s3Client() throws InvalidPortException, InvalidEndpointException {
return new MinioClient(s3Url, accessKey, secretKey);
}
@Bean
public AmazonS3 s3() {
AWSCredentials credentials = new BasicAWSCredentials(accessKey, secretKey);
ClientConfiguration clientConfiguration = new ClientConfiguration();
clientConfiguration.setSignerOverride("AWSS3V4SignerType");
AmazonS3 s3Client = AmazonS3ClientBuilder
.standard()
.withEndpointConfiguration(new AwsClientBuilder.EndpointConfiguration(s3Url, Regions.US_EAST_1.name()))
.withPathStyleAccessEnabled(true)
.withClientConfiguration(clientConfiguration)
.withCredentials(new AWSStaticCredentialsProvider(credentials))
.build();
return s3Client;
}
}
Отлично у нас есть настроенный клиент, теперь можно перейти к написанию endpoint-ов в нашем контроллере StorageController
```java
package com.timurisachenko.microstorage.controllers;
import com.timurisachenko.microstorage.services.AmazonS3Service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.core.io.ByteArrayResource;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@RestController
@RequestMapping("/s3")
public class StorageController {
private AmazonS3Service s3Service;
@Autowired
public StorageController(@Qualifier("amazonS3ServiceImpl") AmazonS3Service s3Service) {
this.s3Service = s3Service;
}
@PostMapping(value = "/{bucketName}/files", consumes = {MediaType.MULTIPART_FORM_DATA_VALUE})
public Map<String, String> upload(@PathVariable("bucketName") String bucketName, @RequestPart(value = "file") MultipartFile files) throws Exception {
s3Service.uploadFile(bucketName, files.getOriginalFilename(), files.getBytes());
Map<String, String> result = new HashMap<>();
result.put("key", files.getOriginalFilename());
return result;
}
@GetMapping(value = "/{bucketName}/{keyName}", consumes = "application/octet-stream")
public ResponseEntity<ByteArrayResource> downloadFile(@PathVariable("bucketName") String bucketName, @PathVariable("keyName") String keyName) throws Exception {
byte[] data = s3Service.downloadFile(bucketName, keyName);
ByteArrayResource resource = new ByteArrayResource(data);
return ResponseEntity
.ok()
.contentLength(data.length)
.header("Content-type", "application/octet-stream")
.header("Content-disposition", "attachment; filename=\"" + keyName + "\"")
.body(resource);
}
@DeleteMapping("/{bucketName}/files/{keyName}")
public void delete(@PathVariable("bucketName") String bucketName, @PathVariable(value = "keyName") String keyName) throws Exception {
s3Service.deleteFile(bucketName, keyName);
}
@GetMapping("/{bucketName}/files")
public List<String> listObjects(@PathVariable("bucketName") String bucketName) throws Exception {
return s3Service.listFiles(bucketName);
}
}
Вот так выглядит наш контроллер, AmazonS3Service - это интерфейс с конечной реализацией процессов по загрузке, скачиванию и т.д.
Ключевыми деталями контроллера являются в большей мере это то, что при загрузке на сервер мы указываем в каком формате приходит запрос на POST метод /s3/{bucketName}/files
- MediaType.MULTIPART_FORM_DATA_VALUE
а также типом запроса служит тип MultipartFile
При скачивании файла - тип ответа возвращается в формате "application/octet-stream"
а возвразаемый объект в формате массива байтов с заголовком disposition
Вернемся к контроллеру, в нем как вы могли заметить был указан AmazonS3Service - это интерфейс, с набором общих методов для обоих клиентов указанных в AppConfig классе.
package com.timurisachenko.microstorage.services;
import java.io.File;
import java.util.List;
public interface AmazonS3Service {
void uploadFile(String bucketName, String originalFilename, byte[] bytes) throws Exception;
byte[] downloadFile(String bucketName, String fileUrl) throws Exception;
void deleteFile(String bucketName, String fileUrl) throws Exception;
List<String> listFiles(String bucketName) throws Exception;
File upload(String bucketName, String name, byte[] content) throws Exception;
byte[] getFile(String bucketName, String key) throws Exception;
}
Перейдем к реализации AmazonS3ServiceImpl
package com.timurisachenko.microstorage.services.impl;
import com.amazonaws.services.s3.AmazonS3;
import com.amazonaws.services.s3.model.S3Object;
import com.amazonaws.services.s3.model.S3ObjectInputStream;
import com.timurisachenko.microstorage.services.AmazonS3Service;
import org.apache.commons.io.IOUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.io.*;
import java.util.LinkedList;
import java.util.List;
@Service
public class AmazonS3ServiceImpl implements AmazonS3Service {
private final AmazonS3 s3;
@Autowired
public AmazonS3ServiceImpl(AmazonS3 s3) {
this.s3 = s3;
}
@Override
public void uploadFile(String bucketName, String originalFilename, byte[] bytes) throws Exception {
File file = upload(bucketName, originalFilename, bytes);
s3.putObject(bucketName, originalFilename, file);
}
@Override
public byte[] downloadFile(String bucketName, String fileUrl) throws Exception {
return getFile(bucketName, fileUrl);
}
@Override
public void deleteFile(String bucketName, String fileUrl) throws Exception {
s3.deleteObject(bucketName, fileUrl);
}
@Override
public List<String> listFiles(String bucketName) throws Exception {
List<String> list = new LinkedList<>();
s3.listObjects(bucketName).getObjectSummaries().forEach(itemResult -> {
list.add(itemResult.getKey());
System.out.println(itemResult.getKey());
});
return list;
}
@Override
public File upload(String bucketName, String name, byte[] content) throws Exception{
File file = new File("/tmp/" + name);
file.canWrite();
file.canRead();
FileOutputStream iofs = null;
iofs = new FileOutputStream(file);
iofs.write(content);
return file;
}
@Override
public byte[] getFile(String bucketName, String key) throws Exception {
S3Object obj = s3.getObject(bucketName, key);
S3ObjectInputStream stream = obj.getObjectContent();
try {
byte[] content = IOUtils.toByteArray(stream);
obj.close();
return content;
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
}
СОБСТВЕННО ЭТО ВСЁ! СПАСИБО ЗА ВНИМАНИЕ!
Если было полезно, или бесполезно - просьба оставить комментарий или написать мне, если есть отличные идеи какой пример разобрать.
Спасибо, что дочитали до сюда!
🙏
Ссылка на github исходники👈
Предыдущий пост введение в MinIO
https://timurisachenko.com/minio/