From 70f99f765c0ccd09775d1ca70f438811cb50a6a2 Mon Sep 17 00:00:00 2001 From: SIWON990327 Date: Fri, 22 Nov 2024 21:16:25 +0900 Subject: [PATCH] =?UTF-8?q?feat=20:=20=ED=9A=8C=EC=9B=90=20=EC=A0=95?= =?UTF-8?q?=EB=B3=B4=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 3 ++ .../com/groom/orbit/S3/S3UploadService.java | 42 +++++++++++++++ .../orbit/common/exception/ErrorCode.java | 2 + .../com/groom/orbit/config/AmazonConfig.java | 53 +++++++++++++++++++ .../response => }/MemberCommandService.java | 22 +++++++- .../dto/request/UpdateMemberRequestDto.java | 8 +++ .../controller/MemberCommandController.java | 18 +++++-- .../orbit/member/dao/jpa/entity/Member.java | 10 ++++ src/main/resources/application.yml | 15 +++++- 9 files changed, 166 insertions(+), 7 deletions(-) create mode 100644 src/main/java/com/groom/orbit/S3/S3UploadService.java create mode 100644 src/main/java/com/groom/orbit/config/AmazonConfig.java rename src/main/java/com/groom/orbit/member/app/{dto/response => }/MemberCommandService.java (75%) create mode 100644 src/main/java/com/groom/orbit/member/app/dto/request/UpdateMemberRequestDto.java diff --git a/build.gradle b/build.gradle index c539342..1716ab8 100644 --- a/build.gradle +++ b/build.gradle @@ -104,6 +104,9 @@ dependencies { // feign client implementation 'org.springframework.cloud:spring-cloud-starter-openfeign' + + // s3 + implementation 'org.springframework.cloud:spring-cloud-starter-aws:2.2.6.RELEASE' } tasks.named('test') { diff --git a/src/main/java/com/groom/orbit/S3/S3UploadService.java b/src/main/java/com/groom/orbit/S3/S3UploadService.java new file mode 100644 index 0000000..56d5407 --- /dev/null +++ b/src/main/java/com/groom/orbit/S3/S3UploadService.java @@ -0,0 +1,42 @@ +package com.groom.orbit.S3; + +import java.io.IOException; +import java.util.UUID; + +import org.springframework.stereotype.Service; +import org.springframework.web.multipart.MultipartFile; + +import com.amazonaws.services.s3.AmazonS3; +import com.amazonaws.services.s3.model.ObjectMetadata; +import com.amazonaws.services.s3.model.PutObjectRequest; +import com.groom.orbit.config.AmazonConfig; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +@Service +@RequiredArgsConstructor +public class S3UploadService { + + private final AmazonS3 amazonS3; + private final AmazonConfig amazonConfig; + + public String uploadFile(MultipartFile file) { + ObjectMetadata metadata = new ObjectMetadata(); + metadata.setContentLength(file.getSize()); + try { + amazonS3.putObject( + new PutObjectRequest( + amazonConfig.getBucket(), generateProfileKeyName(), file.getInputStream(), metadata)); + } catch (IOException e) { + log.error("error at AmazonS3Manager uploadFile : {}", (Object) e.getStackTrace()); + } + + return amazonS3.getUrl(amazonConfig.getBucket(), generateProfileKeyName()).toString(); + } + + public String generateProfileKeyName() { + return "profile" + '/' + UUID.randomUUID().toString(); + } +} diff --git a/src/main/java/com/groom/orbit/common/exception/ErrorCode.java b/src/main/java/com/groom/orbit/common/exception/ErrorCode.java index 29d6aba..04460ae 100644 --- a/src/main/java/com/groom/orbit/common/exception/ErrorCode.java +++ b/src/main/java/com/groom/orbit/common/exception/ErrorCode.java @@ -20,6 +20,7 @@ public enum ErrorCode { NOT_FOUND_RESUME(40403, HttpStatus.NOT_FOUND, "해당 이력이 존재하지 않습니다."), NOT_FOUND_GOAL(40404, HttpStatus.NOT_FOUND, "해당 목표가 존재하지 않습니다."), NOT_FOUND_SCHEDULE(40405, HttpStatus.NOT_FOUND, "해당 일정이 존재하지 않습니다."), + NOT_FOUND_S3(40406, HttpStatus.NOT_FOUND, "해당 파일을 찾을 수 없습니다."), // Invalid Argument Error MISSING_REQUEST_PARAMETER(40000, HttpStatus.BAD_REQUEST, "필수 요청 파라미터가 누락되었습니다."), @@ -31,6 +32,7 @@ public enum ErrorCode { BAD_REQUEST_JSON(40006, HttpStatus.BAD_REQUEST, "잘못된 JSON 형식입니다."), SEARCH_SHORT_LENGTH_ERROR(40007, HttpStatus.BAD_REQUEST, "검색어는 2글자 이상이어야 합니다."), INVALID_ACCESS_URL(40015, HttpStatus.BAD_REQUEST, "잘못된 사용자 접근입니다."), + INVALID_FILE(40008, HttpStatus.BAD_REQUEST, "잘못된 파일입니다."), // Gone Error GONE_SHARED_URL(41001, HttpStatus.GONE, "해당 공유 URL이 만료되었습니다."), diff --git a/src/main/java/com/groom/orbit/config/AmazonConfig.java b/src/main/java/com/groom/orbit/config/AmazonConfig.java new file mode 100644 index 0000000..8790613 --- /dev/null +++ b/src/main/java/com/groom/orbit/config/AmazonConfig.java @@ -0,0 +1,53 @@ +package com.groom.orbit.config; + +import jakarta.annotation.PostConstruct; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import com.amazonaws.auth.AWSCredentials; +import com.amazonaws.auth.AWSCredentialsProvider; +import com.amazonaws.auth.AWSStaticCredentialsProvider; +import com.amazonaws.auth.BasicAWSCredentials; +import com.amazonaws.services.s3.AmazonS3; +import com.amazonaws.services.s3.AmazonS3ClientBuilder; + +import lombok.Getter; + +@Configuration +@Getter +public class AmazonConfig { + @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; + + @Value("${cloud.aws.s3.bucket}") + private String bucket; + + private AWSCredentials awsCredentials; + + @PostConstruct + public void init() { + this.awsCredentials = new BasicAWSCredentials(accessKey, secretKey); + } + + @Bean + public AmazonS3 amazonS3() { + AWSCredentials awsCredentials = new BasicAWSCredentials(accessKey, secretKey); + return AmazonS3ClientBuilder.standard() + .withRegion(region) + .withCredentials(new AWSStaticCredentialsProvider(awsCredentials)) + .build(); + } + + @Bean + public AWSCredentialsProvider awsCredentialsProvider() { + return new AWSStaticCredentialsProvider(awsCredentials); + } +} diff --git a/src/main/java/com/groom/orbit/member/app/dto/response/MemberCommandService.java b/src/main/java/com/groom/orbit/member/app/MemberCommandService.java similarity index 75% rename from src/main/java/com/groom/orbit/member/app/dto/response/MemberCommandService.java rename to src/main/java/com/groom/orbit/member/app/MemberCommandService.java index cadfcb2..5aba08a 100644 --- a/src/main/java/com/groom/orbit/member/app/dto/response/MemberCommandService.java +++ b/src/main/java/com/groom/orbit/member/app/MemberCommandService.java @@ -1,16 +1,19 @@ -package com.groom.orbit.member.app.dto.response; +package com.groom.orbit.member.app; import java.util.List; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.multipart.MultipartFile; +import com.groom.orbit.S3.S3UploadService; +import com.groom.orbit.common.dto.CommonSuccessDto; import com.groom.orbit.config.openai.AiFeedbackRequestDto; import com.groom.orbit.config.openai.AiFeedbackResponseDto; import com.groom.orbit.config.openai.OpenAiClient; import com.groom.orbit.job.app.InterestJobService; import com.groom.orbit.job.app.dto.JobDetailResponseDto; -import com.groom.orbit.member.app.MemberQueryService; +import com.groom.orbit.member.app.dto.request.UpdateMemberRequestDto; import com.groom.orbit.member.dao.jpa.MemberRepository; import com.groom.orbit.member.dao.jpa.entity.Member; import com.groom.orbit.resume.app.ResumeQueryService; @@ -27,6 +30,7 @@ public class MemberCommandService { private final InterestJobService interestJobService; private final ResumeQueryService resumeQueryService; private final MemberQueryService memberQueryService; + private final S3UploadService s3UploadService; private final OpenAiClient openAiClient; private AiFeedbackRequestDto createAiFeedbackRequest(Long memberId) { @@ -63,4 +67,18 @@ public String createAiFeedbackResponse(Long memberId) { return responseDto.getAnswer(); } + + public CommonSuccessDto updateMember( + Long memberId, UpdateMemberRequestDto requestDto, MultipartFile multipartFile) { + + Member member = memberQueryService.findMember(memberId); + + String newProfileUrl = s3UploadService.uploadFile(multipartFile); + + member.updateMember(requestDto, newProfileUrl); + + memberRepository.save(member); + + return CommonSuccessDto.fromEntity(true); + } } diff --git a/src/main/java/com/groom/orbit/member/app/dto/request/UpdateMemberRequestDto.java b/src/main/java/com/groom/orbit/member/app/dto/request/UpdateMemberRequestDto.java new file mode 100644 index 0000000..8aa1ce1 --- /dev/null +++ b/src/main/java/com/groom/orbit/member/app/dto/request/UpdateMemberRequestDto.java @@ -0,0 +1,8 @@ +package com.groom.orbit.member.app.dto.request; + +public record UpdateMemberRequestDto( + String nickname, + String knownPrompt, + String helpPrompt, + Boolean isNotification, + Boolean isProfile) {} diff --git a/src/main/java/com/groom/orbit/member/controller/MemberCommandController.java b/src/main/java/com/groom/orbit/member/controller/MemberCommandController.java index c1967d7..ae6389a 100644 --- a/src/main/java/com/groom/orbit/member/controller/MemberCommandController.java +++ b/src/main/java/com/groom/orbit/member/controller/MemberCommandController.java @@ -1,12 +1,14 @@ package com.groom.orbit.member.controller; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; +import org.springframework.http.MediaType; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; import com.groom.orbit.common.annotation.AuthMember; +import com.groom.orbit.common.dto.CommonSuccessDto; import com.groom.orbit.common.dto.ResponseDto; -import com.groom.orbit.member.app.dto.response.MemberCommandService; +import com.groom.orbit.member.app.MemberCommandService; +import com.groom.orbit.member.app.dto.request.UpdateMemberRequestDto; import lombok.RequiredArgsConstructor; @@ -21,4 +23,12 @@ public class MemberCommandController { public ResponseDto createAiFeedback(@AuthMember Long memberId) { return ResponseDto.ok(memberCommandService.createAiFeedbackResponse(memberId)); } + + @PatchMapping(consumes = {MediaType.MULTIPART_FORM_DATA_VALUE}) + public ResponseDto updateMember( + @AuthMember Long memberId, + @RequestPart(value = "file", required = false) MultipartFile file, + @RequestPart(value = "requestDto") UpdateMemberRequestDto requestDto) { + return ResponseDto.ok(memberCommandService.updateMember(memberId, requestDto, file)); + } } diff --git a/src/main/java/com/groom/orbit/member/dao/jpa/entity/Member.java b/src/main/java/com/groom/orbit/member/dao/jpa/entity/Member.java index e5c7dd9..f8b5d2c 100644 --- a/src/main/java/com/groom/orbit/member/dao/jpa/entity/Member.java +++ b/src/main/java/com/groom/orbit/member/dao/jpa/entity/Member.java @@ -14,6 +14,7 @@ import com.groom.orbit.job.dao.jpa.entity.InterestJob; import com.groom.orbit.job.dao.jpa.entity.Job; +import com.groom.orbit.member.app.dto.request.UpdateMemberRequestDto; import lombok.AllArgsConstructor; import lombok.Getter; @@ -62,4 +63,13 @@ public void addInterestJobs(List jobs) { jobs.stream().map(job -> InterestJob.create(this, job)).toList(); this.interestJobs.addAll(interestJobs); } + + public void updateMember(UpdateMemberRequestDto requestDto, String newProfileUrl) { + this.nickname = requestDto.nickname(); + this.imageUrl = newProfileUrl; + this.knownPrompt = requestDto.knownPrompt(); + this.helpPrompt = requestDto.helpPrompt(); + this.isNotification = requestDto.isNotification(); + this.isProfile = requestDto.isProfile(); + } } diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index cb73442..ecb2254 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -31,4 +31,17 @@ oauth: api: https://kapi.kakao.com --- openai: - api-key: ${OPENAI_API_KEY} \ No newline at end of file + api-key: ${OPENAI_API_KEY} + +--- +cloud: + aws: + s3: + bucket: orbit-dev-env + credentials: + access-key: ${S3_ACCESS_KEY} + secret-key: ${S3_SECRET_KEY} + region: + static: ${REGION} + stack: + auto: false \ No newline at end of file