From b72f4d145a0b9f220da548cb1302492f3dfd888c Mon Sep 17 00:00:00 2001 From: in seong Park <123macanic@naver.com> Date: Mon, 4 Nov 2024 17:39:20 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20AWS=20S3=20FileUploadService=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 파일을 저장하고 해당 파일 URL을 저장하는 로직 구현 추가된 기능: - FileUploadService 로직(CUD 로직) --- build.gradle | 1 + .../mtvs/devlinkbackend/config/S3Config.java | 30 ++++++++ .../file/TestFileController.java | 27 +++++++ .../file/service/FileUploadService.java | 73 +++++++++++++++++++ .../user/command/service/EpicDevService.java | 23 ++++-- src/main/resources/application.yml | 11 ++- 6 files changed, 159 insertions(+), 6 deletions(-) create mode 100644 src/main/java/com/mtvs/devlinkbackend/config/S3Config.java create mode 100644 src/main/java/com/mtvs/devlinkbackend/file/TestFileController.java create mode 100644 src/main/java/com/mtvs/devlinkbackend/file/service/FileUploadService.java diff --git a/build.gradle b/build.gradle index 087b383..b179d0e 100644 --- a/build.gradle +++ b/build.gradle @@ -22,6 +22,7 @@ dependencies { implementation 'org.springframework.boot:spring-boot-starter-security' implementation 'org.springframework.boot:spring-boot-starter-web' implementation 'org.springframework.boot:spring-boot-starter-data-mongodb' + implementation 'org.springframework.cloud:spring-cloud-starter-aws:2.2.6.RELEASE' implementation 'me.paulschwarz:spring-dotenv:4.0.0' implementation 'org.springframework.boot:spring-boot-starter-oauth2-client' // 3.3.x 버전으로 변경 없음 implementation 'io.jsonwebtoken:jjwt-api:0.11.2' diff --git a/src/main/java/com/mtvs/devlinkbackend/config/S3Config.java b/src/main/java/com/mtvs/devlinkbackend/config/S3Config.java new file mode 100644 index 0000000..6dbcb81 --- /dev/null +++ b/src/main/java/com/mtvs/devlinkbackend/config/S3Config.java @@ -0,0 +1,30 @@ +package com.mtvs.devlinkbackend.config; + +import com.amazonaws.auth.AWSStaticCredentialsProvider; +import com.amazonaws.auth.BasicAWSCredentials; +import com.amazonaws.services.s3.AmazonS3Client; +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; + +@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.static}") + private String region; + + @Bean + public AmazonS3Client amazonS3Client() { + BasicAWSCredentials awsCredentials = new BasicAWSCredentials(accessKey, secretKey); + return (AmazonS3Client) AmazonS3ClientBuilder.standard() + .withRegion(region) + .withCredentials(new AWSStaticCredentialsProvider(awsCredentials)) + .build(); + } +} diff --git a/src/main/java/com/mtvs/devlinkbackend/file/TestFileController.java b/src/main/java/com/mtvs/devlinkbackend/file/TestFileController.java new file mode 100644 index 0000000..e910a88 --- /dev/null +++ b/src/main/java/com/mtvs/devlinkbackend/file/TestFileController.java @@ -0,0 +1,27 @@ +package com.mtvs.devlinkbackend.file; + +import com.mtvs.devlinkbackend.file.service.FileUploadService; +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; +import org.springframework.web.multipart.MultipartFile; + +import java.util.List; + +@RestController +@RequestMapping("/api/files") +public class TestFileController { + private final FileUploadService fileUploadService; + + public TestFileController(FileUploadService fileUploadService) { + this.fileUploadService = fileUploadService; + } + + @PostMapping + public ResponseEntity testUpload(@RequestBody MultipartFile[] multipartFiles) { + List fileUrlList = fileUploadService.uploadPublicReadFiles(multipartFiles, "test/"); + return ResponseEntity.ok(fileUrlList); + } +} diff --git a/src/main/java/com/mtvs/devlinkbackend/file/service/FileUploadService.java b/src/main/java/com/mtvs/devlinkbackend/file/service/FileUploadService.java new file mode 100644 index 0000000..b8f313a --- /dev/null +++ b/src/main/java/com/mtvs/devlinkbackend/file/service/FileUploadService.java @@ -0,0 +1,73 @@ +package com.mtvs.devlinkbackend.file.service; + +import com.amazonaws.services.s3.AmazonS3Client; +import com.amazonaws.services.s3.model.CannedAccessControlList; +import com.amazonaws.services.s3.model.DeleteObjectsRequest; +import com.amazonaws.services.s3.model.ObjectMetadata; +import com.amazonaws.services.s3.model.PutObjectRequest; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; +import org.springframework.web.multipart.MultipartFile; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +@Service +public class FileUploadService { + + private final AmazonS3Client amazonS3Client; + + @Value("${cloud.aws.s3.bucket}") + private String bucket; + + public FileUploadService(AmazonS3Client amazonS3Client) { + this.amazonS3Client = amazonS3Client; + } + + public String uploadPublicReadFile(MultipartFile file, String filePath) { + String fileName = filePath + file.getOriginalFilename(); + // filePath는 /를 하지 않고 시작해야 함 + + ObjectMetadata metadata= new ObjectMetadata(); + metadata.setContentType(file.getContentType()); + metadata.setContentLength(file.getSize()); + + try { + amazonS3Client.putObject(new PutObjectRequest(bucket, fileName, file.getInputStream(), metadata) + .withCannedAcl(CannedAccessControlList.PublicRead)); + } catch (IOException e) { + e.printStackTrace(); + throw new RuntimeException(e); + } + + return amazonS3Client.getUrl(bucket, fileName).toString(); + } + + // 여러 파일을 한 번에 저장하는 메서드 추가 + public List uploadPublicReadFiles(MultipartFile[] files, String filePath) { + List fileUrls = new ArrayList<>(); + + for (MultipartFile file : files) { + String fileUrl = uploadPublicReadFile(file, filePath); // 각 파일을 업로드 + fileUrls.add(fileUrl); // 업로드된 파일의 URL을 리스트에 추가 + } + + return fileUrls; + } + + public void deleteFile(String fileName) { + amazonS3Client.deleteObject(bucket, fileName); + } + + public List updatePublicReadFile(List oldFileUrlList, MultipartFile[] newFileList, String filePath) { + for (String fileUrl : oldFileUrlList) { + String fileName = fileUrl.substring(fileUrl.indexOf("/") + 1); + if(amazonS3Client.doesObjectExist(bucket, fileName)) + deleteFile(fileName); + else + throw new IllegalArgumentException("잘못된 파일 경로 삭제 에러"); + } + return uploadPublicReadFiles(newFileList, filePath); + } +} diff --git a/src/main/java/com/mtvs/devlinkbackend/user/command/service/EpicDevService.java b/src/main/java/com/mtvs/devlinkbackend/user/command/service/EpicDevService.java index da65d1b..4c17607 100644 --- a/src/main/java/com/mtvs/devlinkbackend/user/command/service/EpicDevService.java +++ b/src/main/java/com/mtvs/devlinkbackend/user/command/service/EpicDevService.java @@ -1,5 +1,6 @@ package com.mtvs.devlinkbackend.user.command.service; +import com.mtvs.devlinkbackend.file.service.FileUploadService; import com.mtvs.devlinkbackend.user.command.model.dto.request.DevRegistRequestDTO; import com.mtvs.devlinkbackend.user.command.model.dto.request.DevInfoRequestDTO; import com.mtvs.devlinkbackend.user.command.model.dto.request.DevUpdateRequestDTO; @@ -14,6 +15,7 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import java.io.IOException; import java.util.ArrayList; import java.util.List; @@ -23,17 +25,19 @@ public class EpicDevService { private final UserRepository userRepository; private final UserViewRepository userViewRepository; private final DevViewRepository devViewRepository; + private final FileUploadService fileUploadService; - public EpicDevService(DevRepository devRepository, UserRepository userRepository, UserViewRepository userViewRepository, DevViewRepository devViewRepository) { + public EpicDevService(DevRepository devRepository, UserRepository userRepository, UserViewRepository userViewRepository, DevViewRepository devViewRepository, FileUploadService fileUploadService) { this.devRepository = devRepository; this.userRepository = userRepository; this.userViewRepository = userViewRepository; this.devViewRepository = devViewRepository; + this.fileUploadService = fileUploadService; } @Transactional public DevSingleResponseDTO registDev(DevRegistRequestDTO devRegistRequestDTO, - String accountId) { + String accountId) throws IOException { User user = userViewRepository.findUserByEpicAccountId(accountId); if (user == null) { user = new User( @@ -46,13 +50,15 @@ public DevSingleResponseDTO registDev(DevRegistRequestDTO devRegistRequestDTO, User savedUser = userRepository.save(user); DevInfoRequestDTO devInfoRequestDTO = devRegistRequestDTO.getDevInfo(); + List portfolioUrlList = fileUploadService.uploadPublicReadFiles( + devInfoRequestDTO.getPortfolioList(), + "/user/" + user.getUserId() + "/portfolio/"); Dev dev = new Dev( devInfoRequestDTO.getDevName(), devInfoRequestDTO.getDevEmail(), devInfoRequestDTO.getDevPhone(), devInfoRequestDTO.getGithubLink(), - //TODO:: AWS S3 로직 개발 이후 저장 예정 - List.of(""), + portfolioUrlList, devInfoRequestDTO.getCareer(), devInfoRequestDTO.getTag(), devInfoRequestDTO.getHope(), @@ -92,7 +98,14 @@ public DevSingleResponseDTO updateUserPartner(DevUpdateRequestDTO devUpdateReque dev.setDevPhone(devInfoRequestDTO.getDevPhone()); dev.setGithubLink(devInfoRequestDTO.getGithubLink()); //TODO:: 해당 부분 또한 이전 portfolioFile 삭제 이후 새로 업로드한 URL로 업데이트 예정 - dev.setPortfolioUrlList(List.of("")); + + List updatedPortfolioUrlList = + fileUploadService.updatePublicReadFile( + dev.getPortfolioUrlList(), + devInfoRequestDTO.getPortfolioList(), + "/user/" + user.getUserId() + "/portfolio/"); + + dev.setPortfolioUrlList(updatedPortfolioUrlList); dev.setCareer(devInfoRequestDTO.getCareer()); dev.setHope(devInfoRequestDTO.getHope()); diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 920bec4..2925692 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -68,4 +68,13 @@ epicgames: account-uri: https://api.epicgames.dev/epic/id/v2/accounts validate-uri: https://api.epicgames.dev/epic/oauth/v2/tokenInfo server: - port: 8443 \ No newline at end of file + port: 8443 +cloud: + aws: + s3: + bucket: ${AWS_S3_BUCKET_NAME} + region: + static: ${AWS_S3_REGION} + credentials: + access-key: ${AWS_S3_BUCKET_ACCESS_KEY} + secret-key: ${AWS_S3_BUCKET_SECRET_KEY} \ No newline at end of file