Работа с файлами через Yandex Object Storage в Spring Boot

от автора

Yandex Cloud Object Storage — это совместимое с AWS S3 облачное хранилище. В этой статье мы интегрируем его в Spring Boot приложение, используя SDK Амазона.

Создание бакета в Yandex Cloud

Для начала создадим папку, в которой позже будет создано объектное хранилище

Здесь в сервисах находим и создаем s3

Указываем необходимые параметры

И переходим в наше ведерко

Создание аккаунта для доступа к бакету

Как указано в документации, для использования s3 необходимо создать сервисный аккаунт.
Для этого возвращаемся в созданную папку, переходим в Service accounts и создаем новый аккаунт:

Далее возвращаемся в наш бакет и переходим на вкладку security, чтобы добавить роль именно для бакета

Нажимаем на вкладку Assign bindings и добавляем роль editor для нашего аккаунта

Переходим обратно в аккаунт и создаем static access key, сохраняем key id и secret key

Выбираем зависимость для работы с S3

Yandex Object Storage совместим с AWS S3, поэтому именно их sdk мы будем использовать, но есть несколько разных зависимостей, использующих эту sdk.

  1. com.amazonaws:aws-java-sdk:1.x.x — устаревший sdk, который перестанет поддерживаться в этом году

  2. software.amazon.awssdk:s3:2.x.x — актуальный sdk

  3. io.awspring.cloud:spring-cloud-aws-starter-s3 — надстройка над sdk, поддерживаемая комьюинити и улучшающая интеграцию со спрингом

  4. org.springframework.cloud.stream.app:aws-s3-app-starters-common — стартер из проекта spring cloud stream, использующийся для интеграции этого проекта с S3

В этой статье используем актуальную версию sdk (вариант 2), так как нам нужна только базовая функциональность, без лишних абстракций.

Создаем контроллер

Создайте новый spring boot проект и добавьте в следующие зависимости:

implementation("software.amazon.awssdk:aws-sdk-java:2.29.33") implementation("software.amazon.awssdk:apache-client:2.29.33")  implementation("org.springdoc:springdoc-openapi-starter-webmvc-ui:2.7.0") implementation("org.springframework.boot:spring-boot-starter-web")

Обратите внимание, что вместе с sdk нам так же необходимо добавить http-клиент амазона. Также, мы добавили springdoc-openapi для генерации Swagger UI, через который мы будем тестировать приложение.

Создадим контроллер с клиентом s3Client для работы с S3. В настоящем приложении клиента стоит создать отдельным бином и вынести все константы в application.properties, но для простоты я оставлю все в контроллере.

@RestController @RequestMapping("/photos") @Tag(name = "Photos") @ApiResponses(@ApiResponse(responseCode = "200", useReturnTypeSchema = true)) public class PhotoController {      private static final String KEY_ID = "YOUR_KEY_ID";     private static final String SECRET_KEY = "YOUR_SECRET_KEY";     private static final String REGION = "ru-central1";     private static final String S3_ENDPOINT = "https://storage.yandexcloud.net";      private static final String BUCKET = "spring-boot-s3-exmaple";      private final S3Client s3Client;      public PhotoController() {         AwsCredentials credentials = AwsBasicCredentials.create(KEY_ID, SECRET_KEY);          s3Client = S3Client.builder()                 .httpClient(ApacheHttpClient.create())                 .region(Region.of(REGION))                 .endpointOverride(URI.create(S3_ENDPOINT))                 .credentialsProvider(StaticCredentialsProvider.create(credentials))                 .build();     } }

Добавим метод для загрузки фотографий в бакет:

@PutMapping(consumes = MULTIPART_FORM_DATA_VALUE) public String uploadFile(@RequestParam MultipartFile photo) throws IOException {      String key = "photos/" + photo.getOriginalFilename();     PutObjectRequest putObjectRequest = PutObjectRequest.builder()             .bucket(BUCKET)             .key(key)             .contentType(photo.getContentType())             .build();      s3Client.putObject(putObjectRequest, RequestBody.fromBytes(photo.getBytes()));      return key; }

И для получения их из него:

@GetMapping public ResponseEntity<byte[]> downloadFile(@RequestParam String key) throws IOException {      GetObjectRequest objectRequest = GetObjectRequest.builder()             .bucket(BUCKET)             .key(key)             .build();      var inputStream = s3Client.getObject(objectRequest);     byte[] data = inputStream.readAllBytes();      var headers = new HttpHeaders();     headers.add(HttpHeaders.CONTENT_DISPOSITION, "inline");     headers.add(HttpHeaders.CONTENT_TYPE, inputStream.response().contentType());      return ResponseEntity.ok()             .headers(headers)             .body(data); }

Вот как будет выглядеть контроллер полностью:

import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.responses.ApiResponses; import io.swagger.v3.oas.annotations.tags.Tag; import org.springframework.http.HttpHeaders; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; import org.springframework.web.multipart.MultipartFile; import software.amazon.awssdk.auth.credentials.AwsBasicCredentials; import software.amazon.awssdk.auth.credentials.AwsCredentials; import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider; import software.amazon.awssdk.core.sync.RequestBody; import software.amazon.awssdk.http.apache.ApacheHttpClient; import software.amazon.awssdk.regions.Region; import software.amazon.awssdk.services.s3.S3Client; import software.amazon.awssdk.services.s3.model.GetObjectRequest; import software.amazon.awssdk.services.s3.model.PutObjectRequest;  import java.io.IOException; import java.net.URI;  import static org.springframework.http.MediaType.MULTIPART_FORM_DATA_VALUE;  @RestController @RequestMapping("/photos") @Tag(name = "Photos") @ApiResponses(@ApiResponse(responseCode = "200", useReturnTypeSchema = true)) public class PhotoController {      private static final String KEY_ID = "YOR_KEY_ID";     private static final String SECRET_KEY = "YOUR_SECRET_KEY";     private static final String REGION = "ru-central1";     private static final String S3_ENDPOINT = "https://storage.yandexcloud.net";      private static final String BUCKET = "spring-boot-s3-exmaple";      private final S3Client s3Client;      public PhotoController() {         AwsCredentials credentials = AwsBasicCredentials.create(KEY_ID, SECRET_KEY);          s3Client = S3Client.builder()                 .httpClient(ApacheHttpClient.create())                 .region(Region.of(REGION))                 .endpointOverride(URI.create(S3_ENDPOINT))                 .credentialsProvider(StaticCredentialsProvider.create(credentials))                 .build();     }       @PutMapping(consumes = MULTIPART_FORM_DATA_VALUE)     public String uploadFile(@RequestParam MultipartFile photo) throws IOException {          String key = "photos/" + photo.getOriginalFilename();         PutObjectRequest putObjectRequest = PutObjectRequest.builder()                 .bucket(BUCKET)                 .key(key)                 .contentType(photo.getContentType())                 .build();          s3Client.putObject(putObjectRequest, RequestBody.fromBytes(photo.getBytes()));          return key;     }      @GetMapping     public ResponseEntity<byte[]> downloadFile(@RequestParam String key) throws IOException {          GetObjectRequest objectRequest = GetObjectRequest.builder()                 .bucket(BUCKET)                 .key(key)                 .build();          var inputStream = s3Client.getObject(objectRequest);         byte[] data = inputStream.readAllBytes();          var headers = new HttpHeaders();         headers.add(HttpHeaders.CONTENT_DISPOSITION, "inline");         headers.add(HttpHeaders.CONTENT_TYPE, inputStream.response().contentType());          return ResponseEntity.ok()                 .headers(headers)                 .body(data);     } }

Тестируем приложение

Переходим на http://localhost:8080/swagger-ui/index.html и загружаем фото

Далее проверяем, что оно появилось в бакете. Как видите, S3 понимает, что если путь содержит / то нужно создать папку, поэтому наше фото добавилось в папку photos

Пробуем получить фото через наш эндпоинт:

Варианты локального S3 для тестовой среды

Возможно, вам нужен S3 только на проде, а при разработке вы хотели бы использовать локальный сервис. Два таких сервиса — MinIO и LocalStack. Я не буду разворачивать их в этой статье, но коротко сравню:

  • LocalStack — это инструмент для локальной разработки, который эмулирует облачные сервисы AWS. Используется только для разработки и тестирования. Если вы используете не только S3, то LocalStack вам подойдет.

  • MinIO — это объектное хранилище с открытым исходным кодом, совместимое с API Amazon S3. Можно использовать в продакшене.

👨‍💻 Джуниор


ссылка на оригинал статьи https://habr.com/ru/articles/871280/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *