Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[feat] s3를 이용한 presigned URL 구현 #18

Merged
merged 9 commits into from
Jan 6, 2024
Merged
3 changes: 3 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,9 @@ dependencies {

implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
implementation('nz.net.ultraq.thymeleaf:thymeleaf-layout-dialect')

// aws
implementation 'org.springframework.cloud:spring-cloud-starter-aws:2.2.6.RELEASE'
}

tasks.named('test') {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,34 @@
package org.sopt.sweet.domain.gifter.controller;

import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.Schema;
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.sopt.sweet.domain.gifter.dto.request.PresignedURLRequestDto;
import org.sopt.sweet.global.common.SuccessResponse;
import org.springframework.http.ResponseEntity;

@Tag(name = "선물 모임", description = "선물 모임 관련 API")
public interface GifterApi {

@ApiResponses(
value = {
@ApiResponse(responseCode = "201"),
}
)
@Operation(summary = "presigned URL 발급 API")
ResponseEntity<SuccessResponse<?>> getPresignedURL(
@Parameter(
description = "request dto",
required = true,
content = @Content(
schema = @Schema(implementation = PresignedURLRequestDto.class,
required = true),
mediaType = "application/json"
)
) PresignedURLRequestDto presignedURLRequestDto
);
}
Original file line number Diff line number Diff line change
@@ -1,11 +1,26 @@
package org.sopt.sweet.domain.gifter.controller;

import lombok.RequiredArgsConstructor;
import org.sopt.sweet.domain.gifter.dto.request.PresignedURLRequestDto;
import org.sopt.sweet.domain.gifter.dto.response.PresignedURLResponseDto;
import org.sopt.sweet.domain.gifter.service.GifterService;
import org.sopt.sweet.global.common.SuccessResponse;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RequiredArgsConstructor
@RequestMapping("/api/gifter")
@RestController
public class GifterController implements GifterApi {

private final GifterService gifterService;

@PostMapping("/presignedURL")
public ResponseEntity<SuccessResponse<?>> getPresignedURL(@RequestBody PresignedURLRequestDto presignedURLRequestDto) {
final PresignedURLResponseDto presignedURLResponseDto = gifterService.getPresignedURL(presignedURLRequestDto);
return SuccessResponse.created(presignedURLResponseDto);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package org.sopt.sweet.domain.gifter.dto.request;

public record PresignedURLRequestDto(
String fileName
) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package org.sopt.sweet.domain.gifter.dto.response;

import lombok.Builder;

@Builder
public record PresignedURLResponseDto(
String presignedURL
) {
public static PresignedURLResponseDto of(String presignedURL){
return PresignedURLResponseDto.builder()
.presignedURL(presignedURL)
.build();
}
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,21 @@
package org.sopt.sweet.domain.gifter.service;

import lombok.RequiredArgsConstructor;
import org.sopt.sweet.domain.gifter.dto.request.PresignedURLRequestDto;
import org.sopt.sweet.domain.gifter.dto.response.PresignedURLResponseDto;
import org.sopt.sweet.global.config.FileService;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
@RequiredArgsConstructor
@Transactional
public class GifterService {

private final FileService fileService;

public PresignedURLResponseDto getPresignedURL(PresignedURLRequestDto presignedURLRequestDto) {
String URL = fileService.getPreSignedUrl("gifterImg", presignedURLRequestDto.fileName());
return PresignedURLResponseDto.of(URL);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ public interface MemberApi {
@ApiResponse(responseCode = "404", content = @Content)
}
)
@Operation(summary = "프로필을 조회")
@Operation(summary = "스웨거 테스트용 API")
ResponseEntity<SuccessResponse<?>> testSwagger(
);

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package org.sopt.sweet.domain.member.controller;

import lombok.RequiredArgsConstructor;
import org.sopt.sweet.domain.member.dto.request.MemberTokenResponseDto;
import org.sopt.sweet.domain.member.dto.response.MemberTokenResponseDto;
import org.sopt.sweet.domain.member.service.MemberService;
import org.sopt.sweet.global.common.SuccessResponse;
import org.springframework.http.ResponseEntity;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package org.sopt.sweet.domain.member.dto.request;
package org.sopt.sweet.domain.member.dto.response;

import lombok.Builder;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package org.sopt.sweet.domain.member.service;

import lombok.RequiredArgsConstructor;
import org.sopt.sweet.domain.member.dto.request.MemberTokenResponseDto;
import org.sopt.sweet.domain.member.dto.response.MemberTokenResponseDto;
import org.sopt.sweet.global.config.auth.jwt.JwtProvider;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package org.sopt.sweet.domain.opengraph.controller;

import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.Schema;
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.sopt.sweet.domain.gifter.dto.request.PresignedURLRequestDto;
import org.sopt.sweet.domain.opengraph.dto.OpengraphRequestDto;
import org.sopt.sweet.global.common.SuccessResponse;
import org.springframework.http.ResponseEntity;

@Tag(name = "오픈그래프", description = "오픈그래프 관련 API")
public interface OpengraphAPI {
@ApiResponses(
value = {
@ApiResponse(responseCode = "201"),
}
)
@Operation(summary = "오픈그래프 탐색 API")
ResponseEntity<SuccessResponse<?>> getOpenGraph(
@Parameter(
description = "request dto",
required = true,
content = @Content(
schema = @Schema(implementation = OpengraphRequestDto.class,
required = true),
mediaType = "application/json"
)
) OpengraphRequestDto opengraphRequestDto
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import lombok.RequiredArgsConstructor;
import org.sopt.sweet.domain.opengraph.dto.OpengraphResponseDto;
import org.sopt.sweet.domain.opengraph.dto.URLRequestDto;
import org.sopt.sweet.domain.opengraph.dto.OpengraphRequestDto;
import org.sopt.sweet.domain.opengraph.service.OpengraphService;
import org.sopt.sweet.global.common.SuccessResponse;
import org.springframework.http.ResponseEntity;
Expand All @@ -11,14 +11,14 @@
@RequiredArgsConstructor
@RequestMapping("/api/opengraph")
@RestController
public class OpengraphController {
public class OpengraphController implements OpengraphAPI{

private final OpengraphService opengraphService;

@ResponseBody
@GetMapping(value = "")
public ResponseEntity<SuccessResponse<?>> getOpenGraph(@RequestBody URLRequestDto urlRequestDto) {
OpengraphResponseDto opengraphResponseDto = opengraphService.getData(urlRequestDto);
public ResponseEntity<SuccessResponse<?>> getOpenGraph(@RequestBody OpengraphRequestDto opengraphRequestDto) {
OpengraphResponseDto opengraphResponseDto = opengraphService.getData(opengraphRequestDto);
return SuccessResponse.ok(opengraphResponseDto);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package org.sopt.sweet.domain.opengraph.dto;

public record URLRequestDto(
public record OpengraphRequestDto(
String BaseURL
) {
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import lombok.RequiredArgsConstructor;
import org.sopt.sweet.domain.opengraph.dto.OpengraphResponseDto;
import org.sopt.sweet.domain.opengraph.dto.URLRequestDto;
import org.sopt.sweet.domain.opengraph.dto.OpengraphRequestDto;
import org.sopt.sweet.domain.opengraph.lib.OpenGraph;
import org.sopt.sweet.global.error.exception.EntityNotFoundException;
import org.springframework.stereotype.Service;
Expand All @@ -15,9 +15,9 @@
@Transactional
public class OpengraphService {

public OpengraphResponseDto getData(URLRequestDto urlRequestDto) {
public OpengraphResponseDto getData(OpengraphRequestDto opengraphRequestDto) {
try {
OpenGraph page = new OpenGraph(urlRequestDto.BaseURL(), true);
OpenGraph page = new OpenGraph(opengraphRequestDto.BaseURL(), true);
return OpengraphResponseDto.of(getContent(page, "title"), getContent(page, "image"));
} catch (Exception e) {
e.printStackTrace();
Expand Down
50 changes: 50 additions & 0 deletions src/main/java/org/sopt/sweet/global/config/FileService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package org.sopt.sweet.global.config;

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;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

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

@Service
@RequiredArgsConstructor
public class FileService {
@Value("${cloud.aws.s3.bucket}")
private String bucket;

private final AmazonS3 amazonS3;

public String getPreSignedUrl(String prefix, String fileName) {
if (!prefix.equals("")) {
fileName = prefix + "/" + fileName;
}
GeneratePresignedUrlRequest generatePresignedUrlRequest = getGeneratePreSignedUrlRequest(fileName);
URL url = amazonS3.generatePresignedUrl(generatePresignedUrlRequest);
return url.toString();
}

private GeneratePresignedUrlRequest getGeneratePreSignedUrlRequest(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;
}
}
37 changes: 37 additions & 0 deletions src/main/java/org/sopt/sweet/global/config/S3Config.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package org.sopt.sweet.global.config;

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.Primary;

@Configuration
public class S3Config {

@Value("${cloud.aws.credentials.access-key}")
public String accessKey;
@Value("${cloud.aws.credentials.secret-key}")
public String secretKey;
@Value("${cloud.aws.region.static}")
public String region;

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

@Bean
public AmazonS3 amazonS3() {
AmazonS3 s3Builder = AmazonS3ClientBuilder.standard()
.withRegion(region)
.withCredentials(new AWSStaticCredentialsProvider(awsCredentialsProvider()))
.build();
return s3Builder;
}
}