From 70aebf53bb1ac8f2ad8c8abb2853b9a106fdbd7a Mon Sep 17 00:00:00 2001 From: Art Date: Wed, 29 Sep 2021 11:12:42 +0300 Subject: [PATCH] 38.2 Working with S3 (tutorial) - using `aws-java-sdk` (#38) --- README.md | 24 ++++++ support-portal-backend/pom.xml | 7 +- .../backend/config/AmazonConfig.java | 30 +++++++ .../service/S3ProfileImageService.java | 83 +++++++++++++++++++ .../src/main/resources/application.yml | 13 +++ 5 files changed, 156 insertions(+), 1 deletion(-) create mode 100644 support-portal-backend/src/main/java/net/shyshkin/study/fullstack/supportportal/backend/config/AmazonConfig.java create mode 100644 support-portal-backend/src/main/java/net/shyshkin/study/fullstack/supportportal/backend/service/S3ProfileImageService.java diff --git a/README.md b/README.md index 17520d6..cb6160c 100644 --- a/README.md +++ b/README.md @@ -486,3 +486,27 @@ systemctl restart docker - `ng build -c production` - upload to S3 - visit `http://portal.shyshkin.net` + +#### 38 Save Profile Images to S3 + +##### 38.2 Working with S3 (tutorial) + +1. Follow Tutorial + - [How to Upload Files to Amazon S3 in Spring Boot](https://www.section.io/engineering-education/spring-boot-amazon-s3/) +2. Create S3 Bucket + - `portal-user-profile-images` +3. Access and secret keys + - My Security Credentials + - will redirect to `https://console.aws.amazon.com/iam/home?region=eu-north-1#/security_credentials` + - Create Access Key + - Access key ID: `AKIA...2GBJ` + - Secret access key: `LUS...H+yuAW` +4. Adding Amazon SDK dependency + - `` + - ` com.amazonaws` + - ` aws-java-sdk` + - ` 1.12.75` + - `` +5. Create configuration +6. Create ProfileImageService implementation + diff --git a/support-portal-backend/pom.xml b/support-portal-backend/pom.xml index 761ad32..b79dac7 100644 --- a/support-portal-backend/pom.xml +++ b/support-portal-backend/pom.xml @@ -86,6 +86,12 @@ spring-boot-starter-validation + + com.amazonaws + aws-java-sdk + 1.12.75 + + org.springframework.boot spring-boot-starter-test @@ -108,7 +114,6 @@ org.apache.httpcomponents httpclient 4.5.13 - test diff --git a/support-portal-backend/src/main/java/net/shyshkin/study/fullstack/supportportal/backend/config/AmazonConfig.java b/support-portal-backend/src/main/java/net/shyshkin/study/fullstack/supportportal/backend/config/AmazonConfig.java new file mode 100644 index 0000000..6452bc7 --- /dev/null +++ b/support-portal-backend/src/main/java/net/shyshkin/study/fullstack/supportportal/backend/config/AmazonConfig.java @@ -0,0 +1,30 @@ +package net.shyshkin.study.fullstack.supportportal.backend.config; + +import com.amazonaws.auth.AWSCredentials; +import com.amazonaws.auth.AWSStaticCredentialsProvider; +import com.amazonaws.auth.BasicAWSCredentials; +import com.amazonaws.services.s3.AmazonS3; +import com.amazonaws.services.s3.AmazonS3ClientBuilder; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Profile; + +@Configuration +@Profile("image-s3") +public class AmazonConfig { + + @Bean + public AmazonS3 s3(@Value("${app.amazon-s3.access-key}") String accessKey, + @Value("${app.amazon-s3.secret-key}") String secretKey, + @Value("${app.amazon-s3.region}") String region) { + + AWSCredentials awsCredentials = + new BasicAWSCredentials(accessKey, secretKey); + return AmazonS3ClientBuilder + .standard() + .withRegion(region) + .withCredentials(new AWSStaticCredentialsProvider(awsCredentials)) + .build(); + } +} diff --git a/support-portal-backend/src/main/java/net/shyshkin/study/fullstack/supportportal/backend/service/S3ProfileImageService.java b/support-portal-backend/src/main/java/net/shyshkin/study/fullstack/supportportal/backend/service/S3ProfileImageService.java new file mode 100644 index 0000000..b277db4 --- /dev/null +++ b/support-portal-backend/src/main/java/net/shyshkin/study/fullstack/supportportal/backend/service/S3ProfileImageService.java @@ -0,0 +1,83 @@ +package net.shyshkin.study.fullstack.supportportal.backend.service; + +import com.amazonaws.AmazonServiceException; +import com.amazonaws.SdkClientException; +import com.amazonaws.services.s3.AmazonS3; +import com.amazonaws.services.s3.model.ObjectListing; +import com.amazonaws.services.s3.model.ObjectMetadata; +import com.amazonaws.services.s3.model.S3Object; +import com.amazonaws.services.s3.model.S3ObjectInputStream; +import com.amazonaws.util.IOUtils; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import net.shyshkin.study.fullstack.supportportal.backend.exception.domain.ImageStorageException; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Profile; +import org.springframework.stereotype.Service; +import org.springframework.web.multipart.MultipartFile; + +import java.io.IOException; +import java.util.UUID; + +@Slf4j +@Service +@Profile("image-s3") +@RequiredArgsConstructor +public class S3ProfileImageService implements ProfileImageService { + + private final AmazonS3 amazonS3; + + @Value("${app.amazon-s3.bucket-name}") + private String bucketName; + + @Override + public byte[] retrieveProfileImage(UUID userId, String filename) { + + String fileKey = createFileKey(userId, filename); + + try { + S3Object object = amazonS3.getObject(bucketName, fileKey); + S3ObjectInputStream objectContent = object.getObjectContent(); + return IOUtils.toByteArray(objectContent); + } catch (AmazonServiceException | IOException exception) { + throw new ImageStorageException("Failed to download the file from Amazon S3", exception); + } + } + + @Override + public String persistProfileImage(UUID userId, MultipartFile profileImage, String filename) { + +// String fileName = String.format("%s", profileImage.getOriginalFilename()); + String fileKey = createFileKey(userId, filename); + + ObjectMetadata objectMetadata = new ObjectMetadata(); + objectMetadata.addUserMetadata("Content-Type", profileImage.getContentType()); + objectMetadata.addUserMetadata("Content-Length", String.valueOf(profileImage.getSize())); + + try { + amazonS3.putObject(bucketName, fileKey, profileImage.getInputStream(), objectMetadata); + } catch (IOException | SdkClientException exception) { + throw new ImageStorageException("Failed to persist to Amazon S3", exception); + } + + return null; + } + + @Override + public void clearUserStorage(UUID userId) { + + try { + String prefix = userId + "/"; + ObjectListing objectListing = amazonS3.listObjects(bucketName, prefix); + + objectListing.getObjectSummaries() + .forEach(file -> amazonS3.deleteObject(bucketName, file.getKey())); + } catch (SdkClientException exception) { + throw new ImageStorageException("Failed to delete objects from Amazon S3", exception); + } + } + + private String createFileKey(UUID userId, String filename) { + return String.format("%s/%s", userId, filename); + } +} diff --git a/support-portal-backend/src/main/resources/application.yml b/support-portal-backend/src/main/resources/application.yml index d126fc7..f5c3cc1 100644 --- a/support-portal-backend/src/main/resources/application.yml +++ b/support-portal-backend/src/main/resources/application.yml @@ -128,4 +128,17 @@ server.ssl: key-store-password: secret # Keystore password key-store-type: PKCS12 # Keystore format +--- +spring: + config: + activate: + on-profile: image-s3 +app: + amazon-s3: + access-key: ${AMAZON_S3_ACCESS_KEY} + secret-key: ${AMAZON_S3_SECRET_KEY} + region: eu-north-1 + bucket-name: portal-user-profile-images + +