Skip to content

Commit 42c288c

Browse files
authored
feat: S3 업로드, 조회 로직 구현 (#158)
* feat: S3 업로드, 조회 로직 구현 * fix: 매핑 url 오탈자 수정
1 parent 87015b0 commit 42c288c

File tree

4 files changed

+165
-0
lines changed

4 files changed

+165
-0
lines changed

build.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ dependencies {
3333
implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
3434
implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.8.9'
3535
implementation 'io.awspring.cloud:spring-cloud-starter-aws:2.4.4'
36+
implementation 'io.awspring.cloud:spring-cloud-starter-aws-secrets-manager-config:2.4.4'
3637
implementation 'com.opencsv:opencsv:5.11.2'
3738
compileOnly 'org.projectlombok:lombok'
3839
runtimeOnly 'com.mysql:mysql-connector-j'
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
package com.threestar.trainus.domain.file.controller;
2+
3+
import lombok.RequiredArgsConstructor;
4+
import org.springframework.http.HttpStatusCode;
5+
import org.springframework.http.ResponseEntity;
6+
import org.springframework.web.bind.annotation.GetMapping;
7+
import org.springframework.web.bind.annotation.RequestMapping;
8+
import org.springframework.web.bind.annotation.RequestParam;
9+
import org.springframework.web.bind.annotation.RestController;
10+
11+
import com.threestar.trainus.domain.file.dto.GetS3UrlDto;
12+
import com.threestar.trainus.domain.file.service.S3Service;
13+
import com.threestar.trainus.global.annotation.LoginUser;
14+
15+
@RestController
16+
@RequiredArgsConstructor
17+
@RequestMapping("/api/v1/s3")
18+
public class S3Controller {
19+
private final S3Service s3Service;
20+
21+
@GetMapping(value = "/posturl")
22+
public ResponseEntity<GetS3UrlDto> getPostS3Url(@LoginUser Long userId, String filename) {
23+
GetS3UrlDto getS3UrlDto = s3Service.getPostS3Url("image/"+userId, filename);
24+
return new ResponseEntity<>(getS3UrlDto, HttpStatusCode.valueOf(200));
25+
}
26+
27+
@GetMapping(value = "/geturl")
28+
public ResponseEntity<GetS3UrlDto> getGetS3Url(@LoginUser Long userId, @RequestParam String key) {
29+
GetS3UrlDto getS3UrlDto = s3Service.getGetS3Url(userId, key);
30+
return new ResponseEntity<>(getS3UrlDto, HttpStatusCode.valueOf(200));
31+
}
32+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package com.threestar.trainus.domain.file.dto;
2+
3+
import lombok.Builder;
4+
import lombok.EqualsAndHashCode;
5+
import lombok.Getter;
6+
import lombok.NoArgsConstructor;
7+
import lombok.Setter;
8+
9+
@Getter
10+
@Setter
11+
@NoArgsConstructor
12+
@EqualsAndHashCode
13+
public class GetS3UrlDto {
14+
private String preSignedUrl;
15+
private String key;
16+
17+
@Builder
18+
public GetS3UrlDto(String preSignedUrl, String key) {
19+
this.preSignedUrl = preSignedUrl;
20+
this.key = key;
21+
}
22+
}
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
package com.threestar.trainus.domain.file.service;
2+
3+
import java.net.URL;
4+
import java.util.Date;
5+
import java.util.Map;
6+
import java.util.UUID;
7+
8+
import org.springframework.beans.factory.annotation.Value;
9+
import org.springframework.stereotype.Service;
10+
import org.springframework.transaction.annotation.Transactional;
11+
12+
import com.amazonaws.HttpMethod;
13+
import com.amazonaws.services.s3.AmazonS3;
14+
import com.amazonaws.services.s3.Headers;
15+
import com.amazonaws.services.s3.model.CannedAccessControlList;
16+
import com.amazonaws.services.s3.model.GeneratePresignedUrlRequest;
17+
import com.threestar.trainus.domain.file.dto.GetS3UrlDto;
18+
19+
import lombok.RequiredArgsConstructor;
20+
21+
@Service
22+
@RequiredArgsConstructor
23+
public class S3Service {
24+
@Value("${cloud.aws.s3.bucket}")
25+
private String bucket;
26+
27+
private final AmazonS3 amazonS3;
28+
29+
public GetS3UrlDto getPostS3Url(String prefix, String fileName) {
30+
if (!prefix.isEmpty()) {
31+
fileName = createPath(prefix, fileName);
32+
}
33+
34+
GeneratePresignedUrlRequest generatePresignedUrlRequest = getGeneratePresignedUrlRequest(bucket, fileName);
35+
URL url = amazonS3.generatePresignedUrl(generatePresignedUrlRequest);
36+
37+
return GetS3UrlDto.builder()
38+
.preSignedUrl(url.toString())
39+
.key(fileName)
40+
.build();
41+
}
42+
43+
// 파일 업로드용 Presigned URL 생성
44+
private GeneratePresignedUrlRequest getGeneratePresignedUrlRequest(String bucket, String fileName) {
45+
GeneratePresignedUrlRequest generatePresignedUrlRequest = new GeneratePresignedUrlRequest(bucket, fileName)
46+
.withMethod(HttpMethod.PUT)
47+
.withExpiration(getPresignedUrlExpiration());
48+
49+
generatePresignedUrlRequest.addRequestParameter(
50+
Headers.S3_CANNED_ACL,
51+
CannedAccessControlList.PublicRead.toString()
52+
);
53+
54+
return generatePresignedUrlRequest;
55+
}
56+
57+
//Presigned URL 유효기간 설정
58+
private Date getPresignedUrlExpiration() {
59+
Date expiration = new Date();
60+
long expTimeMillis = expiration.getTime();
61+
expTimeMillis += 1000 * 60 * 10;
62+
expiration.setTime(expTimeMillis);
63+
64+
return expiration;
65+
}
66+
67+
//UUID를 사용하여 파일 고유 ID 생성
68+
private String createFileId() {
69+
return UUID.randomUUID().toString();
70+
}
71+
72+
//파일의 전체 경로 생성
73+
private String createPath(String prefix, String fileName) {
74+
String fileId = createFileId();
75+
return String.format("%s/%s", prefix, fileId + "-" + fileName);
76+
}
77+
78+
// get 용 URL 생성
79+
private GeneratePresignedUrlRequest getGetGeneratePresignedUrlRequest(String key, Date expiration) {
80+
return new GeneratePresignedUrlRequest(bucket, key)
81+
.withMethod(HttpMethod.GET)
82+
.withExpiration(expiration);
83+
}
84+
85+
@Transactional(readOnly = true)
86+
public GetS3UrlDto getGetS3Url(Long memberId, String key) {
87+
// url 유효기간 설정하기(1시간)
88+
Date expiration = getExpiration();
89+
90+
// presigned url 생성하기
91+
GeneratePresignedUrlRequest generatePresignedUrlRequest =
92+
getGetGeneratePresignedUrlRequest(key, expiration);
93+
94+
URL url = amazonS3.generatePresignedUrl(generatePresignedUrlRequest);
95+
96+
// return
97+
return GetS3UrlDto.builder()
98+
.preSignedUrl(url.toExternalForm())
99+
.key(key)
100+
.build();
101+
}
102+
103+
private static Date getExpiration() {
104+
Date expiration = new Date();
105+
long expTimeMillis = expiration.getTime();
106+
expTimeMillis += 1000 * 60 * 10; // 10분으로 설정하기
107+
expiration.setTime(expTimeMillis);
108+
return expiration;
109+
}
110+
}

0 commit comments

Comments
 (0)