Skip to content

Commit

Permalink
feat: S3 Presigned URL 발급 구현 (#42)
Browse files Browse the repository at this point in the history
* feat: S3 presigned URL 발급 구현

* refactor: SonarLint에 따라 BasicAWSCredentials를 바로 반환하도록 리팩토링
  • Loading branch information
leeeeeyeon authored Jan 18, 2024
1 parent cc9ba25 commit 2cfc982
Show file tree
Hide file tree
Showing 8 changed files with 155 additions and 2 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -41,3 +41,4 @@ packy-api/src/main/resources/*.yml
packy-api/src/main/resources/*.p8
packy-domain/src/main/resources/*.yml
packy-domain/src/main/resources/*.sql
packy-infra/src/main/resources/*.yml
1 change: 1 addition & 0 deletions packy-api/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ repositories {
dependencies {
// multi module
implementation project(':packy-domain')
implementation project(':packy-infra')

testImplementation platform('org.junit:junit-bom:5.9.1')
testImplementation 'org.junit.jupiter:junit-jupiter'
Expand Down
2 changes: 1 addition & 1 deletion packy-api/src/main/java/com/dilly/ApiApplication.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ public void init() {
}

public static void main(String[] args) {
System.setProperty("spring.config.name", "application-api, application-domain");
System.setProperty("spring.config.name", "application-api, application-domain, application-infra");
SpringApplication.run(ApiApplication.class, args);
}
}
33 changes: 33 additions & 0 deletions packy-api/src/main/java/com/dilly/file/FileController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package com.dilly.file;

import java.util.Map;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import com.dilly.application.FileService;
import com.dilly.global.response.DataResponseDto;

import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;

@Tag(name = "파일 관련 API")
@RestController
@RequestMapping("/api/v1/file")
@RequiredArgsConstructor
public class FileController {

private final FileService fileService;

@Operation(summary = "Presigned URL 생성")
@GetMapping("/presigned-url/{fileName}")
public DataResponseDto<Map<String, String>> getPresignedUrl(
@PathVariable(name = "fileName") @Schema(description = "확장자명을 포함해주세요")
String fileName) {
return DataResponseDto.from(fileService.getPresignedUrl("images", fileName));
}
}
11 changes: 11 additions & 0 deletions packy-infra/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,22 @@ repositories {
dependencies {
testImplementation platform('org.junit:junit-bom:5.9.1')
testImplementation 'org.junit.jupiter:junit-jupiter'

// aws
implementation 'io.awspring.cloud:spring-cloud-starter-aws:2.4.4'
implementation 'io.awspring.cloud:spring-cloud-starter-aws-secrets-manager-config:2.4.4'
}

test {
useJUnitPlatform()
}

processResources.dependsOn('copySecret')
tasks.register('copySecret', Copy) {
from '../packy-submodule/infra'
include '*'
into './src/main/resources'
}

bootJar.enabled = false
jar.enabled = true
69 changes: 69 additions & 0 deletions packy-infra/src/main/java/com/dilly/application/FileService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package com.dilly.application;

import java.net.URL;
import java.util.Date;
import java.util.Map;
import java.util.UUID;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import com.amazonaws.HttpMethod;
import com.amazonaws.services.s3.AmazonS3;
import com.amazonaws.services.s3.Headers;
import com.amazonaws.services.s3.model.CannedAccessControlList;
import com.amazonaws.services.s3.model.GeneratePresignedUrlRequest;

import lombok.RequiredArgsConstructor;

@Service
@RequiredArgsConstructor
public class FileService {

@Value("${cloud.s3.bucket}")
private String bucket;

private final AmazonS3 amazonS3;

public Map<String, String> getPresignedUrl(String prefix, String fileName) {
if (!prefix.isEmpty()) {
fileName = createPath(prefix, fileName);
}

GeneratePresignedUrlRequest generatePresignedUrlRequest = getGeneratePresignedUrlRequest(bucket, fileName);
URL url = amazonS3.generatePresignedUrl(generatePresignedUrlRequest);

return Map.of("url", url.toString());
}

private GeneratePresignedUrlRequest getGeneratePresignedUrlRequest(String bucket, String fileName) {
GeneratePresignedUrlRequest generatePresignedUrlRequest = new GeneratePresignedUrlRequest(bucket, fileName)
.withMethod(HttpMethod.PUT)
.withExpiration(getPresignedUrlExpiration());

generatePresignedUrlRequest.addRequestParameter(
Headers.S3_CANNED_ACL,
CannedAccessControlList.PublicRead.toString()
);

return generatePresignedUrlRequest;
}

private Date getPresignedUrlExpiration() {
Date expiration = new Date();
long expTimeMillis = expiration.getTime();
expTimeMillis += 1000 * 60 * 2;
expiration.setTime(expTimeMillis);

return expiration;
}

private String createFileId() {
return UUID.randomUUID().toString();
}

private String createPath(String prefix, String fileName) {
String fileId = createFileId();
return String.format("%s/%s", prefix, fileId + "-" + fileName);
}
}
38 changes: 38 additions & 0 deletions packy-infra/src/main/java/com/dilly/global/S3Config.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package com.dilly.global;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;

import com.amazonaws.auth.AWSStaticCredentialsProvider;
import com.amazonaws.auth.BasicAWSCredentials;
import com.amazonaws.services.s3.AmazonS3;
import com.amazonaws.services.s3.AmazonS3ClientBuilder;

@Configuration
public class S3Config {

@Value("${cloud.aws.credentials.access-key}")
private String accessKey;

@Value("${cloud.aws.credentials.secret-key}")
private String secretKey;

@Value("${cloud.aws.region}")
private String region;

@Bean
@Primary
public BasicAWSCredentials awsCredentialsProvider() {
return new BasicAWSCredentials(accessKey, secretKey);
}

@Bean
public AmazonS3 amazonS3() {
return AmazonS3ClientBuilder.standard()
.withRegion(region)
.withCredentials(new AWSStaticCredentialsProvider(awsCredentialsProvider()))
.build();
}
}
2 changes: 1 addition & 1 deletion packy-submodule

0 comments on commit 2cfc982

Please sign in to comment.