Skip to content

Commit

Permalink
refactor: 게시글 수정 시 이미지 처리 로직 개선
Browse files Browse the repository at this point in the history
- 프론트에서 모든 이미지 파일을 받아오지 않고 삭제할 이미지의 url을 받아오는 방식으로 변경
- 이때 기존 이미지 - 삭제할 이미지 + 추가할 이미지 개수를 확인해 이미지 조건 확인
- 이미지 삭제 시  해당 게시글에 존재하지 않는 이미지를 제거하지 못하도록 방지하는 로직 추가
  • Loading branch information
yeonjae02 committed Jul 31, 2024
1 parent 600e74b commit e1a998b
Show file tree
Hide file tree
Showing 4 changed files with 143 additions and 20 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,7 @@

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.solucitation.midpoint_backend.domain.community_board.dto.PostDetailDto;
import com.solucitation.midpoint_backend.domain.community_board.dto.PostRequestDto;
import com.solucitation.midpoint_backend.domain.community_board.dto.PostResponseDto;
import com.solucitation.midpoint_backend.domain.community_board.dto.PostUpdateDto;
import com.solucitation.midpoint_backend.domain.community_board.dto.*;
import com.solucitation.midpoint_backend.domain.community_board.service.PostService;

import com.solucitation.midpoint_backend.domain.member.dto.ValidationErrorResponse;
Expand Down Expand Up @@ -249,6 +246,45 @@ public ResponseEntity<?> deletePost(@PathVariable Long postId, Authentication au
}
}

// @PatchMapping(value = "/{postId}", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
// public ResponseEntity<?> updatePost(@PathVariable Long postId,
// Authentication authentication,
// @RequestPart("postDto") String postUpdateDtoJson,
// @RequestPart(value = "postImages", required = false) List<MultipartFile> postImages) throws JsonProcessingException {
// try{
// PostUpdateDto postUpdateDto = objectMapper.readValue(postUpdateDtoJson, PostUpdateDto.class);
//
// if (authentication == null || !authentication.isAuthenticated()) {
// return ResponseEntity.status(HttpStatus.UNAUTHORIZED)
// .body("해당 서비스를 이용하기 위해서는 로그인이 필요합니다.");
// }
//
// String memberEmail = authentication.getName();
// Member member = memberService.getMemberByEmail(memberEmail);
// if (member == null) {
// return ResponseEntity.status(HttpStatus.NOT_FOUND).body("사용자를 찾을 수 없습니다.");
// }
//
// postUpdateDto.validate(); // 제목, 본문, 해시태그 검증
//
// if (postImages != null && !postImages.isEmpty() && postImages.size() > 3) { // 이미지 변경이 있는 경우
// return ResponseEntity.status(HttpStatus.BAD_REQUEST).body("이미지는 최대 3장까지 업로드 가능합니다.");
// }
//
// postService.updatePost(postId, postUpdateDto, member, postImages);
//
// return ResponseEntity.status(HttpStatus.OK).body("게시글을 성공적으로 수정했습니다.");
// } catch (EntityNotFoundException e) {
// return ResponseEntity.status(HttpStatus.NOT_FOUND).body("해당 게시글이 존재하지 않습니다.");
// } catch (AccessDeniedException e) {
// return ResponseEntity.status(HttpStatus.FORBIDDEN).body(e.getMessage());
// } catch (RuntimeException e) {
// return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(e.getMessage());
// } catch (Exception e) {
// return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("게시글 수정 중 오류가 발생하였습니다." + e.getMessage());
// }
// }

@PatchMapping(value = "/{postId}", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
public ResponseEntity<?> updatePost(@PathVariable Long postId,
Authentication authentication,
Expand All @@ -268,10 +304,13 @@ public ResponseEntity<?> updatePost(@PathVariable Long postId,
return ResponseEntity.status(HttpStatus.NOT_FOUND).body("사용자를 찾을 수 없습니다.");
}

postUpdateDto.validate(); // 제목, 본문, 해시태그 검증

if (postImages != null && !postImages.isEmpty() && postImages.size() > 3) { // 이미지 변경이 있는 경우
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body("이미지는 최대 3장까지 업로드 가능합니다.");
int nowImageCnt = postService.getPostById(postId).getImages().size();
postUpdateDto.validate(nowImageCnt); // 제목, 본문, 해시태그, 삭제할 이미지 검증

if (postImages != null && !postImages.isEmpty()) { // 이미지 변경이 있는 경우
int nextImageCnt = nowImageCnt - postUpdateDto.getDeleteImageUrl().size(); // 삭제 작업만 진행했을 때의 이미지 개수
if (nextImageCnt + postImages.size() > 3) // 최종 이미지 개수
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body("이미지는 최대 3장까지 업로드 가능합니다.");
}

postService.updatePost(postId, postUpdateDto, member, postImages);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import lombok.Getter;
import lombok.NoArgsConstructor;

import java.util.ArrayList;
import java.util.List;

@Getter
Expand All @@ -18,7 +19,9 @@ public class PostUpdateDto {

private List<Long> postHashtag;

public void validate() {
private List<String> deleteImageUrl = new ArrayList<>();

public void validate(int imageCnt) {
if (postHashtag != null) {
if (postHashtag.size() != 2 || postHashtag.get(0).equals(postHashtag.get(1))) {
throw new IllegalArgumentException("서로 다른 두 개의 해시태그를 선택해야 합니다.");
Expand All @@ -41,5 +44,18 @@ public void validate() {
throw new IllegalArgumentException("본문은 비워둘 수 없습니다.");
}
}

if (deleteImageUrl != null) {
if (deleteImageUrl.size() >= imageCnt) { // 이미지를 전부 삭제하는 경우 방지
throw new IllegalArgumentException("이미지는 최소 한 장 업로드해야 합니다.");
}

for (String s : deleteImageUrl) { // 삭제할 이미지 정보 1차 검증
String trimmedDeleteImageUrl = s.trim();
if (trimmedDeleteImageUrl.isEmpty()) {
throw new IllegalArgumentException("이미지가 유효하지 않습니다.");
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,23 @@

import com.solucitation.midpoint_backend.domain.community_board.entity.Image;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;

import java.util.List;
import java.util.Optional;

@Repository
public interface ImageRepository extends JpaRepository<Image, Long> {
Optional<Image> findByMemberIdAndPostIsNull(Long memberId); // 회원의 프로필 이미지를 찾는 경우

@Query("SELECT COUNT(i) FROM Image i WHERE i.imageUrl = :imageUrl AND i.post.id = :postId")
Long countByImageUrlAndPostId(@Param("imageUrl") String imageUrl, @Param("postId") Long postId);

@Modifying
@Query("DELETE FROM Image i WHERE i.imageUrl = :imageUrl AND i.post.id = :postId")
void deleteImageByImageUrlAndPostId(@Param("imageUrl") String imageUrl, @Param("postId") Long postId);
}

Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
package com.solucitation.midpoint_backend.domain.community_board.service;

import com.solucitation.midpoint_backend.domain.community_board.dto.PostDetailDto;
import com.solucitation.midpoint_backend.domain.community_board.dto.PostRequestDto;
import com.solucitation.midpoint_backend.domain.community_board.dto.PostResponseDto;
import com.solucitation.midpoint_backend.domain.community_board.dto.PostUpdateDto;
import com.solucitation.midpoint_backend.domain.community_board.dto.*;
import com.solucitation.midpoint_backend.domain.community_board.entity.*;
import com.solucitation.midpoint_backend.domain.community_board.repository.*;
import com.solucitation.midpoint_backend.domain.file.service.S3Service;
Expand Down Expand Up @@ -253,6 +250,50 @@ private Set<Post> wordsToPosts(String[] words) {
return resultSet;
}

// @Transactional
// public void updatePost(Long postId, PostUpdateDto postUpdateDto, Member member, List<MultipartFile> postImages)
// throws AccessDeniedException {
// Post post = postRepository.findById(postId)
// .orElseThrow(() -> new EntityNotFoundException("해당 게시글이 존재하지 않습니다."));
//
// if (!post.getMember().getId().equals(member.getId())) {
// throw new AccessDeniedException("해당 게시글을 수정할 권한이 없습니다. 본인이 작성한 글만 수정할 수 있습니다.");
// }
//
// List<PostHashtag> newPostHashtag = null;
// if (postUpdateDto.getPostHashtag() != null) // 해시태그 변경 시 수정
// newPostHashtag = addHashtags(post, postUpdateDto.getPostHashtag());
//
// List<Image> existImages = new ArrayList<>(post.getImages());
// List<Image> newImages = null;
//
// try {
// if (postImages != null && !postImages.isEmpty()) // 이미지 변경 시 수정
// newImages = addImages(post, member, postImages);
//
// // 기존 이미지 삭제
// if (newImages != null && !newImages.isEmpty()) {
// deleteImages(existImages);
// }
//
// post = Post.builder()
// .id(post.getId())
// .member(post.getMember())
// .createDate(post.getCreateDate())
// .title(postUpdateDto.getTitle() != null ? postUpdateDto.getTitle() : post.getTitle())
// .content(postUpdateDto.getContent() != null ? postUpdateDto.getContent() : post.getContent())
// .postHashtags(newPostHashtag != null && !newPostHashtag.isEmpty() ? newPostHashtag : post.getPostHashtags())
// .images(newImages != null && !newImages.isEmpty() ? newImages : existImages)
// .build();
//
// postRepository.save(post);
// } catch (Exception e) {
// // 트랜잭션 롤백
// TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
// throw e; // 오류를 다시 던집니다.
// }
// }

@Transactional
public void updatePost(Long postId, PostUpdateDto postUpdateDto, Member member, List<MultipartFile> postImages)
throws AccessDeniedException {
Expand All @@ -271,22 +312,31 @@ public void updatePost(Long postId, PostUpdateDto postUpdateDto, Member member,
List<Image> newImages = null;

try {
if (postImages != null && !postImages.isEmpty()) // 이미지 변경 시 수정
newImages = addForUpdateImages(post, member, postImages);
if (postImages != null && !postImages.isEmpty()) {
newImages = addForUpdateImages(post, member, postImages); // 새 이미지 추가
if (newImages != null && !newImages.isEmpty()) {
existImages.addAll(newImages);
}
}

// 기존 이미지 삭제
if (newImages != null && !newImages.isEmpty()) {
deleteImages(existImages);
if (postUpdateDto.getDeleteImageUrl() != null && !postUpdateDto.getDeleteImageUrl().isEmpty()) {
for (String imageUrl : postUpdateDto.getDeleteImageUrl()) {
Long cnt = imageRepository.countByImageUrlAndPostId(imageUrl, postId);
if (cnt == 0) {
throw new RuntimeException("해당 게시글에 존재하지 않는 이미지는 삭제할 수 없습니다.");
}
}
deleteImagesWithUrl(postUpdateDto.getDeleteImageUrl(), postId);
}

post = Post.builder()
.id(post.getId())
.member(post.getMember())
.createDate(post.getCreateDate())
.title(postUpdateDto.getTitle() != null ? postUpdateDto.getTitle() : post.getTitle())
.content(postUpdateDto.getContent() != null ? postUpdateDto.getContent() : post.getContent())
.postHashtags(newPostHashtag != null && !newPostHashtag.isEmpty() ? newPostHashtag : post.getPostHashtags())
.images(newImages != null && !newImages.isEmpty() ? newImages : existImages)
.images(existImages)
.build();

postRepository.save(post);
Expand All @@ -303,7 +353,7 @@ private List<Image> addForUpdateImages(Post post, Member member, List<MultipartF
try {
for (MultipartFile postImage : postImages) {
if (postImage.isEmpty()) // 사용자가 이미지 변경을 요청하지 않음
return null;
return null;
String postImageUrl = s3Service.upload("post-images", postImage.getOriginalFilename(), postImage);

Image image = Image.builder()
Expand All @@ -327,4 +377,11 @@ protected void deleteImages(List<Image> images) {
imageRepository.deleteAll(images);
}

@Transactional
protected void deleteImagesWithUrl(List<String> images, Long postId) {
for (String imageUrl : images) {
s3Service.delete(imageUrl);
imageRepository.deleteImageByImageUrlAndPostId(imageUrl, postId);
}
}
}

0 comments on commit e1a998b

Please sign in to comment.