From ead98d8f74a9bc891c252bcc16aaea44e60243f2 Mon Sep 17 00:00:00 2001 From: jwpark1211 <1211abc@naver.com> Date: Fri, 17 May 2024 01:57:37 +0900 Subject: [PATCH 1/2] build : add s3 dependency --- build.gradle | 1 + 1 file changed, 1 insertion(+) diff --git a/build.gradle b/build.gradle index 47f71d6..f4b4084 100644 --- a/build.gradle +++ b/build.gradle @@ -28,6 +28,7 @@ dependencies { implementation 'org.springframework.boot:spring-boot-starter-webflux' //webClient implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.0.2' //swagger implementation 'org.springframework.boot:spring-boot-starter-data-redis' //redis + implementation 'org.springframework.cloud:spring-cloud-starter-aws:2.2.6.RELEASE' //s3 compileOnly 'org.projectlombok:lombok' runtimeOnly 'com.mysql:mysql-connector-j' annotationProcessor 'org.projectlombok:lombok' From e60e7edbb2841b78ecaeeda32fdacbbfc3cf2df9 Mon Sep 17 00:00:00 2001 From: jwpark1211 <1211abc@naver.com> Date: Fri, 17 May 2024 01:58:00 +0900 Subject: [PATCH 2/2] feat : member profile img api --- .../capstone/bookitty/common/S3Config.java | 30 ++++++++ .../domain/controller/MemberController.java | 7 +- .../bookitty/domain/entity/Member.java | 9 +++ .../domain/service/MemberService.java | 32 ++++++--- .../bookitty/domain/service/S3Service.java | 72 +++++++++++++++++++ 5 files changed, 140 insertions(+), 10 deletions(-) create mode 100644 src/main/java/capstone/bookitty/common/S3Config.java create mode 100644 src/main/java/capstone/bookitty/domain/service/S3Service.java diff --git a/src/main/java/capstone/bookitty/common/S3Config.java b/src/main/java/capstone/bookitty/common/S3Config.java new file mode 100644 index 0000000..719f8e0 --- /dev/null +++ b/src/main/java/capstone/bookitty/common/S3Config.java @@ -0,0 +1,30 @@ +package capstone.bookitty.common; + +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 awsCreds = new BasicAWSCredentials(accessKey, secretKey); + return (AmazonS3Client) AmazonS3ClientBuilder.standard() + .withRegion(region) + .withCredentials(new AWSStaticCredentialsProvider(awsCreds)) + .build(); + } +} diff --git a/src/main/java/capstone/bookitty/domain/controller/MemberController.java b/src/main/java/capstone/bookitty/domain/controller/MemberController.java index 3eab624..cbb0a57 100644 --- a/src/main/java/capstone/bookitty/domain/controller/MemberController.java +++ b/src/main/java/capstone/bookitty/domain/controller/MemberController.java @@ -13,6 +13,9 @@ import org.springframework.data.web.PageableDefault; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; + +import java.io.IOException; import static capstone.bookitty.domain.dto.MemberDTO.*; @@ -81,7 +84,7 @@ public ResponseEntity findAllMembers( memberService.getAllMemberInfo(pageable))); } - /* TODO : S3 + @Operation(summary = "회원 프로필 업로드 / requestPart 이름 : profile") @PostMapping(path = "/{id}/profile") public ResponseEntity updateMemberProfile( @PathVariable("id") Long memberId, @@ -89,7 +92,7 @@ public ResponseEntity updateMemberProfile( ) throws IOException { memberService.updateProfile(memberId, profileImg); return ResponseEntity.ok().build(); - }*/ + } @Operation(summary = "회원 탈퇴") @DeleteMapping(path = "/{id}") diff --git a/src/main/java/capstone/bookitty/domain/entity/Member.java b/src/main/java/capstone/bookitty/domain/entity/Member.java index 0ca0dab..8cd4e41 100644 --- a/src/main/java/capstone/bookitty/domain/entity/Member.java +++ b/src/main/java/capstone/bookitty/domain/entity/Member.java @@ -44,6 +44,15 @@ public Member(String name, String email, String password, String profileImg, this.password = password; this.birthDate = birthDate; this.createdAt = LocalDateTime.now(); + this.profileImg = "https://bookitty-bucket.s3.ap-northeast-2.amazonaws.com/Jiji.jpeg"; this.grade = Grade.USER; } + + public void updateProfile(String profileImg){ + this.profileImg = profileImg; + } + + public void deleteProfile(){ + this.profileImg = "https://bookitty-bucket.s3.ap-northeast-2.amazonaws.com/Jiji.jpeg"; + } } diff --git a/src/main/java/capstone/bookitty/domain/service/MemberService.java b/src/main/java/capstone/bookitty/domain/service/MemberService.java index 017b0fb..26689f3 100644 --- a/src/main/java/capstone/bookitty/domain/service/MemberService.java +++ b/src/main/java/capstone/bookitty/domain/service/MemberService.java @@ -8,8 +8,12 @@ import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.multipart.MultipartException; +import org.springframework.web.multipart.MultipartFile; +import java.io.IOException; + import static capstone.bookitty.domain.dto.MemberDTO.*; @Service @@ -17,6 +21,7 @@ @RequiredArgsConstructor public class MemberService { private final MemberRepository memberRepository; + private final S3Service s3Service; @Transactional public IdResponse saveMember(MemberSaveRequest request) { @@ -58,16 +63,27 @@ public Page getAllMemberInfo(Pageable pageable) { .map(MemberInfoResponse::of); } - /*TODO: S3 관련 처리 @Transactional public void updateProfile(Long memberId, MultipartFile profileImg) - throws MultipartException { - if(profileImg.isEmpty()) - throw new MultipartException("The file is not valid."); - Member member = memberRepository.findById(memberId) - .orElseThrow(()-> new EntityNotFoundException("member not found.")); - member.updateImg(url); - }*/ + throws MultipartException, IOException { + try { + if (profileImg.isEmpty()) { + throw new MultipartException("The file is not valid."); + } + Member member = memberRepository.findById(memberId) + .orElseThrow(() -> new EntityNotFoundException("member not found.")); + String imageUrl = s3Service.uploadFile(profileImg); + member.updateProfile(imageUrl); + } catch (MultipartException e) { + throw e; + } catch (EntityNotFoundException e) { + throw e; + } catch (IOException e) { + throw e; + } catch (Exception e) { + throw new RuntimeException("An unexpected error occurred while updating the profile.", e); + } + } //TODO : SPRING SECURITY AUTHORITY SETTING @Transactional diff --git a/src/main/java/capstone/bookitty/domain/service/S3Service.java b/src/main/java/capstone/bookitty/domain/service/S3Service.java new file mode 100644 index 0000000..bcc1f6f --- /dev/null +++ b/src/main/java/capstone/bookitty/domain/service/S3Service.java @@ -0,0 +1,72 @@ +package capstone.bookitty.domain.service; + +import com.amazonaws.AmazonServiceException; +import com.amazonaws.SdkClientException; +import com.amazonaws.services.s3.AmazonS3; +import com.amazonaws.services.s3.model.*; +import lombok.RequiredArgsConstructor; +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.List; + +@Service +@RequiredArgsConstructor +public class S3Service { + @Value("${cloud.aws.s3.bucket}") + private String bucket; + private final AmazonS3 amazonS3; + + public String uploadFile(MultipartFile multipartFile) throws IOException { + String fileName = multipartFile.getOriginalFilename(); + + //파일 형식 구하기 + String ext = fileName.split("\\.")[1]; + String contentType = ""; + + //content type을 지정해서 올려주지 않으면 자동으로 "application/octet-stream"으로 고정이 + // 돼서 링크 클릭시 웹에서 열리는게 아니라 자동 다운이 시작됨. + switch (ext) { + case "jpeg": + contentType = "image/jpeg"; + break; + case "png": + contentType = "image/png"; + break; + case "txt": + contentType = "text/plain"; + break; + case "csv": + contentType = "text/csv"; + break; + } + + try { + ObjectMetadata metadata = new ObjectMetadata(); + metadata.setContentType(contentType); + + amazonS3.putObject(new PutObjectRequest(bucket, fileName, multipartFile.getInputStream(), metadata) + .withCannedAcl(CannedAccessControlList.PublicRead)); + } catch (AmazonServiceException e) { + e.printStackTrace(); + } catch (SdkClientException e) { + e.printStackTrace(); + } + + //object 정보 가져오기 + ListObjectsV2Result listObjectsV2Result = amazonS3.listObjectsV2(bucket); + List objectSummaries = listObjectsV2Result.getObjectSummaries(); + + for (S3ObjectSummary object: objectSummaries) { + System.out.println("object = " + object.toString()); + } + return amazonS3.getUrl(bucket, fileName).toString(); + } + + public void deleteImage(String filename){ + amazonS3.deleteObject(new DeleteObjectRequest(bucket,filename)); + } + +} \ No newline at end of file