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

[#60] s3에 파일(이미지) 업로드하는 api 구현 #85

Merged
merged 9 commits into from
Jan 29, 2023
4 changes: 3 additions & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,9 @@ dependencies {
implementation 'org.springframework.boot:spring-boot-starter-validation'
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-oauth2-client' // OAuth2-Client dependency
implementation 'org.springframework.boot:spring-boot-starter-actuator'
implementation 'org.springframework.boot:spring-boot-starter-actuator' // Actuator dependency
implementation 'org.springframework.cloud:spring-cloud-starter-aws:2.2.6.RELEASE' // AWS S3 dependency

testImplementation 'org.springframework.boot:spring-boot-starter-test'
testImplementation 'org.springframework.security:spring-security-test'
annotationProcessor 'org.springframework.boot:spring-boot-configuration-processor'
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package com.prgrms.prolog.global.image.api;

import java.io.File;
import java.util.UUID;

import org.springframework.context.annotation.Profile;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestPart;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;

import com.prgrms.prolog.global.image.dto.UploadFileResponse;
import com.prgrms.prolog.global.image.util.FileManager;
import com.prgrms.prolog.global.image.util.UploadFileToS3;

import lombok.RequiredArgsConstructor;

@Profile("!test")
@RestController
@RequiredArgsConstructor
public class ImageController {

private static final String FILE_PATH = "posts";
private final FileManager fileManager;
private final UploadFileToS3 uploadFileToS3;

@PostMapping("/file")
public UploadFileResponse uploadFile(
@RequestPart(value = "file") MultipartFile multipartFile
) {
String id = UUID.randomUUID().toString();
String originalFilename = multipartFile.getOriginalFilename();
String fileName = (originalFilename + id).replace(" ", "");

File tempTargetFile = fileManager.convertMultipartFileToFile(multipartFile)
.orElseThrow(() -> new IllegalArgumentException("exception.file.convert"));
String fileUrl = uploadFileToS3.upload(tempTargetFile, FILE_PATH, fileName);

fileManager.removeFile(tempTargetFile);
return UploadFileResponse.toUploadFileResponse(originalFilename, fileUrl);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.prgrms.prolog.global.image.dto;

public record UploadFileResponse(
String originalFileName,
String uploadFilePath) {

public static UploadFileResponse toUploadFileResponse(String originalFileName, String uploadFilePath) {
return new UploadFileResponse(originalFileName, uploadFilePath);
}
}
43 changes: 43 additions & 0 deletions src/main/java/com/prgrms/prolog/global/image/util/FileManager.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package com.prgrms.prolog.global.image.util;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Optional;

import org.springframework.context.annotation.Profile;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;

import lombok.extern.slf4j.Slf4j;

@Profile("!test")
@Slf4j
@Component
public class FileManager {

public Optional<File> convertMultipartFileToFile(MultipartFile multipartFile) {
File newFile = new File(multipartFile.getOriginalFilename());
try {
if (newFile.createNewFile()) {
log.debug(newFile.getName() + " : 임시 파일을 생성했습니다");
try (FileOutputStream fos = new FileOutputStream(newFile)) {
fos.write(multipartFile.getBytes());
}
return Optional.of(newFile);
}
} catch (IOException e) {
throw new RuntimeException("exception.file.io");
}
return Optional.empty();
}

public void removeFile(File targetFile) {
String fileName = targetFile.getName();
if (targetFile.delete()) {
log.debug(fileName + " : 임시 파일를 삭제했습니다");
} else {
log.debug(fileName + " : 임시 파일를 삭제하지 못했습니다.");
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package com.prgrms.prolog.global.image.util;

import java.io.File;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Profile;
import org.springframework.stereotype.Component;

import com.amazonaws.services.s3.AmazonS3Client;
import com.amazonaws.services.s3.model.PutObjectRequest;

import lombok.RequiredArgsConstructor;

@Profile("!test")
@Component
@RequiredArgsConstructor
public class UploadFileToS3 {

private final AmazonS3Client amazonS3Client;

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

public String upload(File uploadFile, String dirName, String fileName) {
String savedFileName = dirName + "/" + fileName;
amazonS3Client.putObject(new PutObjectRequest(bucket, savedFileName, uploadFile));
return amazonS3Client.getUrl(bucket, savedFileName).toString();
}
}
12 changes: 12 additions & 0 deletions src/main/resources/application-aws.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
cloud:
aws:
s3:
bucket: prolog-storage
credentials:
access-key: ${AWS_ACCESS_KEY}
secret-key: ${AWS_SECRET_KEY}
region:
static: ap-northeast-2
auto: false
stack:
auto: false
3 changes: 2 additions & 1 deletion src/main/resources/application.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,10 @@ spring:
- db
- exception
- security
- aws

management:
endpoints:
web:
exposure:
include: '*'
include: '*'
6 changes: 5 additions & 1 deletion src/main/resources/messages/exceptions/exception.properties
Original file line number Diff line number Diff line change
Expand Up @@ -35,4 +35,8 @@ exception.jwtAuthentication.user.email.notExists=유저 이메일이 존재하
exception.jwtAuthenticationToken.isAuthenticated=인증 정보를 확인할 수 없는 메서드 주입은 지원하지 않습니다. 생성자를 통해 생성해야 합니다.
## LIKE ##
exception.like.notExist=좋아요를 누르지않아 취소할 수 없습니다.
exception.like.alreadyExist=이미 좋아요를 누른 게시물에는 좋아요를 할 수 없습니다.
exception.like.alreadyExist=이미 좋아요를 누른 게시물에는 좋아요를 할 수 없습니다.
## FILE ##
exception.file.convert=\uD30C\uC77C\uC744 \uBCC0\uD658\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.
exception.file.io=\uD30C\uC77C \uC0DD\uC131\uAD00\uB828 \uC624\uB958

31 changes: 10 additions & 21 deletions src/test/resources/application.yml
Original file line number Diff line number Diff line change
@@ -1,28 +1,17 @@
spring:
config:
import: optional:file:.env[.properties]
profiles:
include:
- security
- aws

flyway:
enabled: false

datasource:
driver-class-name: org.testcontainers.jdbc.ContainerDatabaseDriver
url: jdbc:tc:mysql:8.0.31:///test?TC_INITSCRIPT=schema.sql
security:
oauth2:
client:
registration:
kakao:
client-name: kakao
client-id: ${CLIENT_ID}
client-secret: ${CLIENT_SECRET}
scope: profile_nickname, account_email
redirect-uri: ${REDIRECT_URI}
authorization-grant-type: authorization_code
client-authentication-method: POST
provider:
kakao:
authorization-uri: https://kauth.kakao.com/oauth/authorize
token-uri: https://kauth.kakao.com/oauth/token
user-info-uri: https://kapi.kakao.com/v2/user/me
user-name-attribute: id

jwt:
issuer: prgrms
secret-key: prgrmsbackenddevrteamprologkwonj
expiry-seconds: 3
expiry-seconds: 2