From 05fe7af7aa1fe7198fba76053e1c9f23618d2001 Mon Sep 17 00:00:00 2001 From: soljjang777 Date: Sun, 17 Nov 2024 22:54:52 +0900 Subject: [PATCH 1/2] =?UTF-8?q?[refactor]=20#101-=20S3=20=EA=B4=80?= =?UTF-8?q?=EB=A0=A8=20=EA=B8=B0=EB=8A=A5=EC=9D=84=20=EA=B3=B5=ED=86=B5=20?= =?UTF-8?q?=EC=9C=A0=ED=8B=B8=EB=A1=9C=20=EC=B6=94=EC=B6=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/resume/ResumeServiceImpl.java | 38 +++------- .../com/wooribound/global/util/S3Util.java | 69 +++++++++++++++++++ src/main/resources/application.properties | 2 + 3 files changed, 80 insertions(+), 29 deletions(-) create mode 100644 src/main/java/com/wooribound/global/util/S3Util.java diff --git a/src/main/java/com/wooribound/domain/resume/ResumeServiceImpl.java b/src/main/java/com/wooribound/domain/resume/ResumeServiceImpl.java index 76fa55a..4cc9024 100644 --- a/src/main/java/com/wooribound/domain/resume/ResumeServiceImpl.java +++ b/src/main/java/com/wooribound/domain/resume/ResumeServiceImpl.java @@ -1,8 +1,6 @@ package com.wooribound.domain.resume; import com.amazonaws.services.s3.AmazonS3Client; -import com.amazonaws.services.s3.model.DeleteObjectRequest; -import com.amazonaws.services.s3.model.ObjectMetadata; import com.wooribound.domain.resume.dto.ResumeDTO; import com.wooribound.domain.resume.dto.ResumeDetailDTO; import com.wooribound.domain.wbuser.WbUser; @@ -11,6 +9,7 @@ import com.wooribound.domain.workhistory.WorkHistoryRepository; import com.wooribound.global.exception.NotEntityException; import com.wooribound.global.util.AuthenticateUtil; +import com.wooribound.global.util.S3Util; import lombok.RequiredArgsConstructor; import org.springframework.beans.factory.annotation.Value; import org.springframework.security.core.Authentication; @@ -18,10 +17,7 @@ import org.springframework.web.multipart.MultipartFile; import java.io.IOException; -import java.net.URLDecoder; -import java.nio.charset.StandardCharsets; import java.util.List; -import java.util.Objects; import java.util.Optional; import java.util.UUID; import java.util.stream.Collectors; @@ -30,10 +26,10 @@ @Service public class ResumeServiceImpl implements ResumeService { - @Value("${cloud.aws.s3.bucketName}") - private String bucket; + @Value("${aws.s3.wbUser.folder}") + String wbUserFolderName; - private final AmazonS3Client amazonS3Client; + private final S3Util s3Util; private final AuthenticateUtil authenticateUtil; private final ResumeRepository resumeRepository; private final WbUserRepository wbUserRepository; @@ -77,29 +73,22 @@ public ResumeDTO registerResume(Authentication authentication, MultipartFile use String userId = authenticateUtil.CheckWbUserAuthAndGetUserId(authentication); Optional byIdWbUser = wbUserRepository.findById(userId); - if (byIdWbUser.isEmpty()) { throw new NotEntityException(); } long resumeId = 1L; Optional maxResumeId = resumeRepository.getMaxResumeId(); - if (maxResumeId.isPresent()) { resumeId = maxResumeId.get() + 1; } - String uploadFileName = createFileName(userImg.getOriginalFilename()); - - ObjectMetadata metadata= new ObjectMetadata(); - metadata.setContentType(userImg.getContentType()); - metadata.setContentLength(userImg.getSize()); - amazonS3Client.putObject(bucket,uploadFileName,userImg.getInputStream(),metadata); + String fileURL = s3Util.uploadFile(userImg, wbUserFolderName); Resume resume = Resume.builder() .resumeId(resumeId) .wbUser(byIdWbUser.get()) - .userImg(amazonS3Client.getUrl(bucket, uploadFileName).toString()) + .userImg(fileURL) .resumeEmail(resumeEmail) .userIntro(userIntro) .build(); @@ -135,21 +124,12 @@ public ResumeDTO updateResume(Authentication authentication, MultipartFile userI if (userImg != null) { // s3에서 파일 삭제 - String url = byIdResume.get().getUserImg(); - String encodedFileName = url.substring(url.lastIndexOf("/") + 1); - String deleteFileName = URLDecoder.decode(encodedFileName, StandardCharsets.UTF_8); - - amazonS3Client.deleteObject(new DeleteObjectRequest(bucket, deleteFileName)); + s3Util.deleteFile(byIdResume.get().getUserImg(), wbUserFolderName); // s3에 새로운 파일 업로드 - String uploadFileName = createFileName(userImg.getOriginalFilename()); - ObjectMetadata metadata= new ObjectMetadata(); - metadata.setContentType(userImg.getContentType()); - metadata.setContentLength(userImg.getSize()); - - amazonS3Client.putObject(bucket,uploadFileName,userImg.getInputStream(),metadata); + String fileURL = s3Util.uploadFile(userImg, wbUserFolderName); - resume.setUserImg(amazonS3Client.getUrl(bucket, uploadFileName).toString()); + resume.setUserImg(fileURL); } resume.setResumeEmail(resumeEmail); diff --git a/src/main/java/com/wooribound/global/util/S3Util.java b/src/main/java/com/wooribound/global/util/S3Util.java new file mode 100644 index 0000000..381b313 --- /dev/null +++ b/src/main/java/com/wooribound/global/util/S3Util.java @@ -0,0 +1,69 @@ +package com.wooribound.global.util; + +import com.amazonaws.AmazonServiceException; +import com.amazonaws.SdkClientException; +import com.amazonaws.services.s3.AmazonS3Client; +import com.amazonaws.services.s3.model.DeleteObjectRequest; +import com.amazonaws.services.s3.model.ObjectMetadata; +import lombok.RequiredArgsConstructor; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; +import org.springframework.web.multipart.MultipartFile; + +import java.io.IOException; +import java.net.URLDecoder; +import java.nio.charset.StandardCharsets; +import java.util.UUID; + +@RequiredArgsConstructor +@Component +public class S3Util { + + @Value("${cloud.aws.s3.bucketName}") + private String bucket; + + private final AmazonS3Client amazonS3Client; + + // S3에 파일 업로드 + public String uploadFile(MultipartFile multipartFile, String s3FolderName) { + try { + String uploadFileName = createFileName(multipartFile.getOriginalFilename()); + + ObjectMetadata metadata = new ObjectMetadata(); + metadata.setContentType(multipartFile.getContentType()); + metadata.setContentLength(multipartFile.getSize()); + + amazonS3Client.putObject(bucket + s3FolderName, uploadFileName, multipartFile.getInputStream(), metadata); + + return amazonS3Client.getUrl(bucket + s3FolderName, uploadFileName).toString(); + + } catch (AmazonServiceException e) { + e.printStackTrace(); + throw new RuntimeException("S3에 파일 업로드 중 오류가 발생했습니다", e); + } catch (SdkClientException e) { + e.printStackTrace(); + throw new RuntimeException("클라이언트 오류가 S3에 업로드 중 발생했습니다", e); + } catch (IOException e) { + e.printStackTrace(); + throw new RuntimeException("파일 입력 중 오류가 발생했습니다", e); + } + + } + + public void deleteFile(String fileURL, String s3FolderName) { + try { + String encodedFileName = fileURL.substring(fileURL.lastIndexOf("/") + 1); + String deleteFileName = URLDecoder.decode(encodedFileName, StandardCharsets.UTF_8); + + amazonS3Client.deleteObject(new DeleteObjectRequest(bucket + s3FolderName, deleteFileName)); + } catch (AmazonServiceException e) { + e.printStackTrace(); + throw new RuntimeException("S3에서 파일 삭제 중 오류가 발생했습니다", e); + } + } + + // 파일 이름 생성 메소드 + private String createFileName(String fileName) { + return UUID.randomUUID().toString().concat(fileName); + } +} diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 03743b2..7b704be 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -58,3 +58,5 @@ cloud.aws.credentials.secretKey=${AWS_SECRET_KEY} cloud.aws.s3.bucketName=${AWS_BUCKET_NAME} cloud.aws.region.static=${AWS_REGION} cloud.aws.stack.auto-=false +aws.s3.wbUser.folder=${S3_WBUSER_FOLDER} +aws.s3.jobPosting.folder=${S3_JOB_POSTING_FOLDER} From 9b1fe245577cd4e180a8e923153eb0710efc3909 Mon Sep 17 00:00:00 2001 From: soljjang777 Date: Sun, 17 Nov 2024 23:11:36 +0900 Subject: [PATCH 2/2] =?UTF-8?q?[update]=20#101=20-=20=EA=B8=B0=EC=97=85?= =?UTF-8?q?=ED=9A=8C=EC=9B=90=20=EA=B3=B5=EA=B3=A0=20=EB=93=B1=EB=A1=9D=20?= =?UTF-8?q?API=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../EnterpriseJobPostingController.java | 7 ++-- .../api/corporate/dto/JobPostingReqDTO.java | 18 +++++----- .../wooribound/domain/job/JobRepository.java | 5 ++- .../domain/jobposting/JobPosting.java | 4 --- .../jobposting/JobPostingRepository.java | 5 +++ .../Service/EntJobPostingServiceImpl.java | 35 ++++++++++++++----- 6 files changed, 48 insertions(+), 26 deletions(-) diff --git a/src/main/java/com/wooribound/api/corporate/controller/EnterpriseJobPostingController.java b/src/main/java/com/wooribound/api/corporate/controller/EnterpriseJobPostingController.java index 3520e8a..8682b0d 100644 --- a/src/main/java/com/wooribound/api/corporate/controller/EnterpriseJobPostingController.java +++ b/src/main/java/com/wooribound/api/corporate/controller/EnterpriseJobPostingController.java @@ -9,6 +9,7 @@ import com.wooribound.domain.userapply.dto.ApplicantResultReqDTO; import com.wooribound.global.util.AuthenticateUtil; import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; import org.springframework.security.core.Authentication; import org.springframework.web.bind.annotation.*; @@ -23,9 +24,9 @@ public class EnterpriseJobPostingController { private final AuthenticateUtil authenticateUtil; // 1. 공고 등록 - @PostMapping("/register") - public String createJobPosting(Authentication authentication, @RequestBody JobPostingReqDTO jobPostingReqDTO) { - return enterpriseJobPostingFacade.createJobPosting(authentication, jobPostingReqDTO); + @PostMapping(value = "/register", consumes = {"multipart/form-data"}) + public ResponseEntity createJobPosting(Authentication authentication, @ModelAttribute JobPostingReqDTO jobPostingReqDTO) { + return ResponseEntity.ok().body(enterpriseJobPostingFacade.createJobPosting(authentication, jobPostingReqDTO)); } // 2. 내 기업 공고 목록 조회 diff --git a/src/main/java/com/wooribound/api/corporate/dto/JobPostingReqDTO.java b/src/main/java/com/wooribound/api/corporate/dto/JobPostingReqDTO.java index 3e36d4c..638e6f3 100644 --- a/src/main/java/com/wooribound/api/corporate/dto/JobPostingReqDTO.java +++ b/src/main/java/com/wooribound/api/corporate/dto/JobPostingReqDTO.java @@ -1,24 +1,22 @@ package com.wooribound.api.corporate.dto; -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Getter; -import lombok.Setter; +import lombok.*; +import org.springframework.format.annotation.DateTimeFormat; +import org.springframework.web.multipart.MultipartFile; import java.util.Date; @Builder -@Getter -@Setter @AllArgsConstructor +@Data public class JobPostingReqDTO { -// private String entId; - private String postImg; + private MultipartFile postImg; private String postTitle; + @DateTimeFormat(pattern = "yyyy-MM-dd") private Date startDate; + @DateTimeFormat(pattern = "yyyy-MM-dd") private Date endDate; - // TODO: Figma, 프론트 직무선택 컴포넌트 추가하기 [공고등록 화면] - private String jobName; + private Long jobId; diff --git a/src/main/java/com/wooribound/domain/job/JobRepository.java b/src/main/java/com/wooribound/domain/job/JobRepository.java index 846e498..940b41b 100644 --- a/src/main/java/com/wooribound/domain/job/JobRepository.java +++ b/src/main/java/com/wooribound/domain/job/JobRepository.java @@ -1,9 +1,12 @@ package com.wooribound.domain.job; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; import org.springframework.stereotype.Repository; +import java.util.Optional; + @Repository public interface JobRepository extends JpaRepository { - Job findByJobName(String jobName); + } diff --git a/src/main/java/com/wooribound/domain/jobposting/JobPosting.java b/src/main/java/com/wooribound/domain/jobposting/JobPosting.java index 5ae0b5a..3f297d9 100644 --- a/src/main/java/com/wooribound/domain/jobposting/JobPosting.java +++ b/src/main/java/com/wooribound/domain/jobposting/JobPosting.java @@ -24,10 +24,6 @@ ) public class JobPosting { @Id - @GeneratedValue( - strategy = GenerationType.AUTO, - generator = "job_posting_seq_generator" - ) @Column(name = "post_id") private Long postId; diff --git a/src/main/java/com/wooribound/domain/jobposting/JobPostingRepository.java b/src/main/java/com/wooribound/domain/jobposting/JobPostingRepository.java index 34b0904..57cc1ac 100644 --- a/src/main/java/com/wooribound/domain/jobposting/JobPostingRepository.java +++ b/src/main/java/com/wooribound/domain/jobposting/JobPostingRepository.java @@ -10,6 +10,7 @@ import org.springframework.stereotype.Repository; import java.util.List; +import java.util.Optional; @Repository public interface JobPostingRepository extends JpaRepository { @@ -91,4 +92,8 @@ List findJobPostingsNew(@Param("exJobs") List exJo "WHERE wh.job.jobId = :jobId " + "ORDER BY w.jobPoint DESC") List findApplicantRecommendation(@Param("jobId") int jobId); + + + @Query("SELECT MAX(jp.postId) FROM JobPosting jp") + Optional getMaxJobPostingId(); } diff --git a/src/main/java/com/wooribound/domain/jobposting/Service/EntJobPostingServiceImpl.java b/src/main/java/com/wooribound/domain/jobposting/Service/EntJobPostingServiceImpl.java index 7a3e763..de69bef 100644 --- a/src/main/java/com/wooribound/domain/jobposting/Service/EntJobPostingServiceImpl.java +++ b/src/main/java/com/wooribound/domain/jobposting/Service/EntJobPostingServiceImpl.java @@ -1,5 +1,6 @@ package com.wooribound.domain.jobposting.Service; +import com.amazonaws.services.s3.AmazonS3Client; import com.wooribound.api.corporate.dto.ApplicantsDTO; import com.wooribound.api.corporate.dto.JobPostingReqDTO; import com.wooribound.domain.enterprise.Enterprise; @@ -19,20 +20,24 @@ import com.wooribound.global.constant.ApplyResult; import com.wooribound.global.constant.YN; import com.wooribound.global.util.RedisUtil; +import com.wooribound.global.util.S3Util; +import jakarta.persistence.EntityNotFoundException; import lombok.RequiredArgsConstructor; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; -import java.util.Calendar; -import java.util.Date; -import java.util.List; -import java.util.Optional; +import java.util.*; import java.util.stream.Collectors; @RequiredArgsConstructor @Service public class EntJobPostingServiceImpl implements EntJobPostingService { + @Value("${aws.s3.jobPosting.folder}") + String jobPostingFolderName; + + private final S3Util s3Util; private static final Logger logger = LogManager.getLogger(EntJobPostingServiceImpl.class); private final JobPostingRepository jobPostingRepository; @@ -40,22 +45,35 @@ public class EntJobPostingServiceImpl implements EntJobPostingService { private final EnterpriseRepository enterpriseRepository; private final UserApplyRepository userApplyRepository; private final NotificationRepository notificationRepository; - private final RedisUtil redisUtil; // 1. 공고 등록 @Override public String createJobPosting(String entId, JobPostingReqDTO jobPostingReqDTO) { Enterprise enterprise = enterpriseRepository.findById(entId).orElse(null); - Job job = jobRepository.findByJobName(jobPostingReqDTO.getJobName()); + + Optional byIdJob = jobRepository.findById(jobPostingReqDTO.getJobId()); + if (byIdJob.isEmpty()) { + throw new EntityNotFoundException(); + } + + Long jobPostingId = 1L; + Optional maxJobPostingId = jobPostingRepository.getMaxJobPostingId(); + if (maxJobPostingId.isPresent()) { + jobPostingId = maxJobPostingId.get() + 1; + } + + String imageURL = s3Util.uploadFile(jobPostingReqDTO.getPostImg(), jobPostingFolderName); JobPosting jobPosting = JobPosting.builder() + .postId(jobPostingId) .enterprise(enterprise) - .job(job) + .job(byIdJob.get()) .postTitle(jobPostingReqDTO.getPostTitle()) - .postImg(jobPostingReqDTO.getPostImg()) + .postImg(imageURL) .startDate(jobPostingReqDTO.getStartDate()) .endDate(jobPostingReqDTO.getEndDate()) + .isDeleted(YN.N) .build(); jobPostingRepository.save(jobPosting); @@ -204,4 +222,5 @@ public List getApplicantRecommendation(int jobId) { }).collect(Collectors.toList()); } + } \ No newline at end of file