From 0e0f1be77a88bfc902eee049ecbd0975e25cea4a Mon Sep 17 00:00:00 2001 From: Yunhyuk Jeon <137145628+airoca@users.noreply.github.com> Date: Fri, 17 Jan 2025 07:41:51 +0900 Subject: [PATCH] =?UTF-8?q?[FEAT]=20=EA=B2=8C=EC=8B=9C=EB=AC=BC=20?= =?UTF-8?q?=EC=82=AC=EC=A7=84=20S3=20=EC=97=85=EB=A1=9C=EB=93=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../post/controller/PostController.java | 36 +++++++++-- .../domain/post/dto/PostCreateDTO.java | 16 +++++ .../dto/request/PostCreateRequestDTO.java | 4 +- .../domain/post/service/PostService.java | 61 ++++++++++++------- .../infra/service/AwsFileService.java | 37 ++++++++--- 5 files changed, 115 insertions(+), 39 deletions(-) create mode 100644 src/main/java/com/spoony/spoony_server/domain/post/dto/PostCreateDTO.java diff --git a/src/main/java/com/spoony/spoony_server/domain/post/controller/PostController.java b/src/main/java/com/spoony/spoony_server/domain/post/controller/PostController.java index a817fd8..8624cb7 100644 --- a/src/main/java/com/spoony/spoony_server/domain/post/controller/PostController.java +++ b/src/main/java/com/spoony/spoony_server/domain/post/controller/PostController.java @@ -1,19 +1,28 @@ package com.spoony.spoony_server.domain.post.controller; import com.spoony.spoony_server.common.dto.ResponseDTO; +import com.spoony.spoony_server.domain.post.dto.PostCreateDTO; import com.spoony.spoony_server.domain.post.dto.request.PostCreateRequestDTO; import com.spoony.spoony_server.domain.post.dto.response.PostResponseDTO; import com.spoony.spoony_server.domain.post.service.PostService; +import com.spoony.spoony_server.infra.service.AwsFileService; import lombok.RequiredArgsConstructor; import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; + +import java.io.IOException; +import java.util.List; @RestController @RequestMapping("/api/post") @RequiredArgsConstructor public class PostController { + private final PostService postService; + private final AwsFileService awsFileService; @GetMapping("/{postId}") public ResponseEntity> getPost(@PathVariable Long postId) { @@ -21,10 +30,29 @@ public ResponseEntity> getPost(@PathVariable Long p return ResponseEntity.status(HttpStatus.OK).body(ResponseDTO.success(postResponse)); } - @PostMapping - public ResponseEntity> createPost(@RequestBody PostCreateRequestDTO postCreateRequestDTO) { - postService.createPost(postCreateRequestDTO); + @PostMapping(consumes = MediaType.MULTIPART_FORM_DATA_VALUE) + public ResponseEntity> createPost( + @RequestPart("data") PostCreateRequestDTO postCreateRequestDTO, + @RequestPart("photos") List photos + ) throws IOException { + List photoUrlList = awsFileService.savePostImages(photos); + + PostCreateDTO updatedPostCreateDTO = new PostCreateDTO( + postCreateRequestDTO.userId(), + postCreateRequestDTO.title(), + postCreateRequestDTO.description(), + postCreateRequestDTO.placeName(), + postCreateRequestDTO.placeAddress(), + postCreateRequestDTO.placeRoadAddress(), + postCreateRequestDTO.latitude(), + postCreateRequestDTO.longitude(), + postCreateRequestDTO.categoryId(), + postCreateRequestDTO.menuList(), + photoUrlList + ); + + postService.createPost(updatedPostCreateDTO); - return ResponseEntity.status(HttpStatus.OK).body(ResponseDTO.success(null)); + return ResponseEntity.ok(ResponseDTO.success(null)); } } diff --git a/src/main/java/com/spoony/spoony_server/domain/post/dto/PostCreateDTO.java b/src/main/java/com/spoony/spoony_server/domain/post/dto/PostCreateDTO.java new file mode 100644 index 0000000..904bb08 --- /dev/null +++ b/src/main/java/com/spoony/spoony_server/domain/post/dto/PostCreateDTO.java @@ -0,0 +1,16 @@ +package com.spoony.spoony_server.domain.post.dto; + +import java.util.List; + +public record PostCreateDTO(Long userId, + String title, + String description, + String placeName, + String placeAddress, + String placeRoadAddress, + Double latitude, + Double longitude, + Long categoryId, + List menuList, + List photoUrlList) { +} diff --git a/src/main/java/com/spoony/spoony_server/domain/post/dto/request/PostCreateRequestDTO.java b/src/main/java/com/spoony/spoony_server/domain/post/dto/request/PostCreateRequestDTO.java index 3878742..afcc1f4 100644 --- a/src/main/java/com/spoony/spoony_server/domain/post/dto/request/PostCreateRequestDTO.java +++ b/src/main/java/com/spoony/spoony_server/domain/post/dto/request/PostCreateRequestDTO.java @@ -11,7 +11,5 @@ public record PostCreateRequestDTO(Long userId, Double latitude, Double longitude, Long categoryId, - List menuList, - String photo) { - // photo 임시 String으로 설정 + List menuList) { } diff --git a/src/main/java/com/spoony/spoony_server/domain/post/service/PostService.java b/src/main/java/com/spoony/spoony_server/domain/post/service/PostService.java index 13a29bd..ed7cc6c 100644 --- a/src/main/java/com/spoony/spoony_server/domain/post/service/PostService.java +++ b/src/main/java/com/spoony/spoony_server/domain/post/service/PostService.java @@ -7,7 +7,7 @@ import com.spoony.spoony_server.common.message.UserErrorMessage; import com.spoony.spoony_server.domain.place.entity.PlaceEntity; import com.spoony.spoony_server.domain.place.repository.PlaceRepository; -import com.spoony.spoony_server.domain.post.dto.request.PostCreateRequestDTO; +import com.spoony.spoony_server.domain.post.dto.PostCreateDTO; import com.spoony.spoony_server.domain.post.dto.response.PostResponseDTO; import com.spoony.spoony_server.domain.post.entity.*; import com.spoony.spoony_server.domain.post.repository.*; @@ -21,10 +21,12 @@ import com.spoony.spoony_server.domain.user.entity.RegionEntity; import com.spoony.spoony_server.domain.user.entity.UserEntity; import com.spoony.spoony_server.domain.user.repository.UserRepository; +import com.spoony.spoony_server.infra.service.AwsFileService; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import java.io.IOException; import java.time.LocalDateTime; import java.util.List; import java.util.stream.Collectors; @@ -47,11 +49,14 @@ public class PostService { private final FollowRepository followRepository; private final FeedRepository feedRepository; + private final AwsFileService awsFileService; + @Transactional public PostResponseDTO getPostById(Long postId) { PostEntity postEntity = postRepository.findById(postId).orElseThrow(() -> new BusinessException(PostErrorMessage.NOT_FOUND_ERROR)); UserEntity userEntity = postEntity.getUser(); + if (userEntity == null) { throw new BusinessException(PostErrorMessage.NOT_FOUND_ERROR); } @@ -64,8 +69,7 @@ public PostResponseDTO getPostById(Long postId) { PlaceEntity place = postEntity.getPlace(); LocalDateTime latestDate = postEntity.getUpdatedAt().isAfter(postEntity.getCreatedAt()) ? postEntity.getUpdatedAt() : postEntity.getCreatedAt(); String formattedDate = latestDate.toLocalDate().toString(); - Long zzim_count = postRepository.countByPostId(postId); - //List category_list = List.of(categoryEntity.getCategoryName()); + Long zzimCount = postRepository.countByPostId(postId); String category = categoryEntity.getCategoryName(); List menuEntityList = menuRepository.findByPost(postEntity).orElseThrow(() -> new BusinessException(PostErrorMessage.NOT_FOUND_ERROR)); @@ -74,27 +78,40 @@ public PostResponseDTO getPostById(Long postId) { .map(menuEntity -> menuEntity.getMenuName()) .collect(Collectors.toList()); - return new PostResponseDTO(postId, userEntity.getUserId(), userEntity.getUserName(), regionEntity.getRegionName(), category, postEntity.getTitle(), formattedDate, menuList, postEntity.getDescription(), - place.getPlaceName(), place.getPlaceAddress(), place.getLatitude(), place.getLongitude(), zzim_count + return new PostResponseDTO( + postId, + userEntity.getUserId(), + userEntity.getUserName(), + regionEntity.getRegionName(), + category, + postEntity.getTitle(), + formattedDate, + menuList, + postEntity.getDescription(), + place.getPlaceName(), + place.getPlaceAddress(), + place.getLatitude(), + place.getLongitude(), + zzimCount ); } @Transactional - public void createPost(PostCreateRequestDTO postCreateRequestDTO) { + public void createPost(PostCreateDTO postCreateDTO) throws IOException { // 게시글 업로드 - UserEntity userEntity = userRepository.findById(postCreateRequestDTO.userId()) + UserEntity userEntity = userRepository.findById(postCreateDTO.userId()) .orElseThrow(() -> new BusinessException(UserErrorMessage.NOT_FOUND_ERROR)); - CategoryEntity categoryEntity = categoryRepository.findById(postCreateRequestDTO.categoryId()) + CategoryEntity categoryEntity = categoryRepository.findById(postCreateDTO.categoryId()) .orElseThrow(() -> new BusinessException(CategoryErrorMessage.NOT_FOUND_ERROR)); PlaceEntity placeEntity = PlaceEntity.builder() - .placeName(postCreateRequestDTO.placeName()) - .placeAddress(postCreateRequestDTO.placeAddress()) - .placeRoadAddress(postCreateRequestDTO.placeRoadAddress()) - .latitude(postCreateRequestDTO.latitude()) - .longitude(postCreateRequestDTO.longitude()) + .placeName(postCreateDTO.placeName()) + .placeAddress(postCreateDTO.placeAddress()) + .placeRoadAddress(postCreateDTO.placeRoadAddress()) + .latitude(postCreateDTO.latitude()) + .longitude(postCreateDTO.longitude()) .build(); placeRepository.save(placeEntity); @@ -102,8 +119,8 @@ public void createPost(PostCreateRequestDTO postCreateRequestDTO) { PostEntity postEntity = PostEntity.builder() .user(userEntity) .place(placeEntity) - .title(postCreateRequestDTO.title()) - .description(postCreateRequestDTO.description()) + .title(postCreateDTO.title()) + .description(postCreateDTO.description()) .createdAt(LocalDateTime.now()) .updatedAt(LocalDateTime.now()) .build(); @@ -117,19 +134,19 @@ public void createPost(PostCreateRequestDTO postCreateRequestDTO) { postCategoryRepository.save(postCategoryEntity); - postCreateRequestDTO.menuList().stream() + postCreateDTO.menuList().stream() .map(menuName -> MenuEntity.builder() .post(postEntity) .menuName(menuName) .build()) .forEach(menuRepository::save); - PhotoEntity photoEntity = PhotoEntity.builder() - .post(postEntity) - .photoUrl(postCreateRequestDTO.photo()) - .build(); - - photoRepository.save(photoEntity); + postCreateDTO.photoUrlList().stream() + .map(photoUrl -> PhotoEntity.builder() + .post(postEntity) + .photoUrl(photoUrl) + .build()) + .forEach(photoRepository::save); // 작성자 스푼 개수 조정 ActivityEntity activityEntity = activityRepository.findById(2L) diff --git a/src/main/java/com/spoony/spoony_server/infra/service/AwsFileService.java b/src/main/java/com/spoony/spoony_server/infra/service/AwsFileService.java index 712a90c..c6fd87d 100644 --- a/src/main/java/com/spoony/spoony_server/infra/service/AwsFileService.java +++ b/src/main/java/com/spoony/spoony_server/infra/service/AwsFileService.java @@ -1,7 +1,6 @@ package com.spoony.spoony_server.infra.service; import com.amazonaws.services.s3.AmazonS3Client; -import com.amazonaws.services.s3.model.CannedAccessControlList; import com.amazonaws.services.s3.model.ObjectMetadata; import com.amazonaws.services.s3.model.PutObjectRequest; import com.spoony.spoony_server.common.exception.BusinessException; @@ -17,6 +16,8 @@ import java.io.File; import java.io.FileOutputStream; import java.io.IOException; +import java.util.ArrayList; +import java.util.List; import java.util.Optional; import java.util.UUID; @@ -34,28 +35,36 @@ public class AwsFileService { private String PROFILE_IMG_DIR = "profile/"; private String POST_IMG_DIR = "post/"; - public String savePhoto(MultipartFile multipartFile, Long memberId) throws IOException { + public String saveProfileImage(MultipartFile multipartFile) throws IOException { File uploadFile = convert(multipartFile) .orElseThrow(() -> new BusinessException(S3ErrorMessage.FILE_CHANGE_FAIL)); - return upload(uploadFile, POST_IMG_DIR, memberId); + return upload(uploadFile, PROFILE_IMG_DIR); } - public String saveProfileImg(MultipartFile multipartFile, Long memberId) throws IOException { + public String savePostImage(MultipartFile multipartFile) throws IOException { File uploadFile = convert(multipartFile) .orElseThrow(() -> new BusinessException(S3ErrorMessage.FILE_CHANGE_FAIL)); - return upload(uploadFile, PROFILE_IMG_DIR, memberId); + return upload(uploadFile, POST_IMG_DIR); } - private String upload(File uploadFile, String dirName, Long memberId) { - String fileName = dirName + memberId + "/" + UUID.randomUUID() + uploadFile.getName(); // S3에 저장된 파일 이름 + public List savePostImages(List photos) throws IOException { + List photoUrls = new ArrayList<>(); + for (MultipartFile photo : photos) { + String photoUrl = savePostImage(photo); // 개별 파일 업로드 + photoUrls.add(photoUrl); + } + return photoUrls; + } + + private String upload(File uploadFile, String dirName) { + String fileName = dirName + "/" + UUID.randomUUID() + uploadFile.getName(); // S3에 저장된 파일 이름 String uploadImageUrl = putS3(uploadFile, fileName); removeNewFile(uploadFile); return uploadImageUrl; } private String putS3(File uploadFile, String fileName) { - amazonS3Client.putObject(new PutObjectRequest(bucket, fileName, uploadFile).withCannedAcl( - CannedAccessControlList.PublicRead)); + amazonS3Client.putObject(new PutObjectRequest(bucket, fileName, uploadFile)); return amazonS3Client.getUrl(bucket, fileName).toString(); } @@ -68,7 +77,15 @@ private void removeNewFile(File targetFile) { } private Optional convert(MultipartFile file) throws IOException { - File convertFile = new File(System.getProperty("user.home") + "/" + file.getOriginalFilename()); + String photosDir = "src/main/resources/photos/"; + + File directory = new File(photosDir); + if (!directory.exists()) { + directory.mkdirs(); + } + + File convertFile = new File(photosDir + file.getOriginalFilename()); + if (convertFile.createNewFile()) { try (FileOutputStream fos = new FileOutputStream(convertFile)) { fos.write(file.getBytes());