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.
-
com.amazonaws:aws-java-sdk:1.x.x — устаревший sdk, который перестанет поддерживаться в этом году
-
software.amazon.awssdk:s3:2.x.x — актуальный sdk
-
io.awspring.cloud:spring-cloud-aws-starter-s3 — надстройка над sdk, поддерживаемая комьюинити и улучшающая интеграцию со спрингом
-
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/
Добавить комментарий