1. 도메인 엔티티 구현
Post
@Entity
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@DynamicUpdate
@Table(name = "posts")
public class Post {
// ... (필드 생략)
// ... (빌더 생략)
// 게시글 수정을 위한 setter 메서드
public void setTitle(String title) { this.title = title; }
public void setContent(String content) { this.content = content; }
public void setType(ETopic type) { this.type = type; }
}
Post 엔티티에 수정 기능을 위한 setter 메서드들을 추가했어. 게시글의 제목, 내용, 타입을 수정할 수 있다.
2. 레포지토리 구현
@Repository
public interface ImageRepository extends JpaRepository<Image, Long> {
// ... (기존 메서드 생략)
boolean existsByPost(Post post);
}
`existsByPost` 메서드를 추가해 특정 게시글에 연관된 이미지가 존재하는지 확인한다. 게시글 수정 시 이미지 처리 로직에 사용하기 위해 생성했다.
3. 컨트롤러 구현
@RestController
@RequiredArgsConstructor
@RequestMapping("/api/v1/posts")
public class PostController {
// ... (기존 메서드 생략)
// 게시글 수정
@PutMapping(value = "/{postId}", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
public ResponseDto<PostRegisterResponseDto> updatePost(@UserId Long userId, // 사용자 ID
@PathVariable Long postId, // 게시글 ID
@Valid @ModelAttribute("postUpdateRequest") PostRegisterRequestDto postRegisterRequestDto, // 수정할 게시글 정보
@RequestPart(value = "imageFiles", required = false) List<MultipartFile> imageFiles // 수정할 이미지 파일들 (선택)
) {
List<MultipartFile> file = Optional.ofNullable(imageFiles).orElse(Collections.emptyList());
postService.updatePost(userId, postId, postRegisterRequestDto, file);
return ResponseDto.ok(new PostRegisterResponseDto(postId));
}
}
사용자 ID, 게시글 ID, 수정할 게시글 정보, 이미지 파일을 선택적으로 입력받아 서비스에 전달한다.
PUT vs PATCH
게시글 핵심 정보와 게시글 관련 이미지를 모두 변경하기 때문에 PUT Method를 선택했다. 구현하고 나서 다시 PUT과 PATCH에 대해 좀 더 찾아봤는데, 리소스의 부분적인 수정은 PATCH가 더 적합한 것 같다.
- PUT
- 리소스의 완전한 교체를 의미한다.
- 클라이언트는 리소스의 전체 표현을 보내야 한다.
- 요청에 포함되지 않은 기존 속성은 제거되거나 기본값으로 재설정되어야 한다.
- PATCH
- 리소스의 부분적인 수정을 의미한다.
- 클라이언트는 변경하려는 부분만 보낼 수 있다.
- 요청에 포함되지 않은 속성은 변경되지 않고 그대로 유지된다.
아래의 서비스 로직을 보면 리소스 전체 수정보다는 부분적인 수정에 더 가깝기 때문에, 코드 리펙토링 진행 시에 PATCH로 변경하면 좋을 것 같다.
4. 서비스 구현
@Service
@RequiredArgsConstructor
public class PostService {
// ... (기존 코드 생략)
@Transactional
public Long updatePost(Long userId, Long postId, PostRegisterRequestDto postRegisterRequestDto, List<MultipartFile> imageFiles) {
// 사용자 존재 여부 확인
userRepository.findById(userId).orElseThrow(() -> new CommonException(ErrorCode.NOT_FOUND_USER));
// 게시글 존재 여부 확인
Post post =
postRepository.findById(postId).orElseThrow(() -> new CommonException(ErrorCode.NOT_FOUND_POST));
// DB에 해당 게시글의 이미지가 존재하는지 확인
Boolean isExistDbImage = imageRepository.existsByPost(post);
try {
// 게시글 정보 업데이트
post.setTitle(postRegisterRequestDto.title());
post.setContent(postRegisterRequestDto.content());
post.setType(postRegisterRequestDto.type());
// 이미지 처리 로직
if(!imageFiles.isEmpty()) { // 이미지 수정 요청 O
if(isExistDbImage) { // 기존 이미지 O
deleteImages(post); // 기존 이미지 모두 삭제
uploadImages(post.getId(), imageFiles); // 요청 이미지 모두 업로드
} else { // 기존 이미지 X
uploadImages(post.getId(), imageFiles); // 요청 이미지 모두 업로드
}
} else if(imageFiles.isEmpty() && isExistDbImage) { // 이미지 수정 요청 X, 기존 이미지 O
deleteImages(post); // 기존 이미지 모두 삭제
}
return post.getId();
} catch (Exception e) {
throw new CommonException(ErrorCode.POST_ERROR);
}
}
@Transactional
private void deleteImages(Post post) {
// DB에서 image 삭제
imageRepository.deleteImagesByPost(post);
// S3에서 image 삭제
DeleteObjectsRequest deleteObjectsRequest = new DeleteObjectsRequest(bucket);
// 해당 경로로 시작하는 모든 객체를 삭제 대상에 추가함
deleteObjectsRequest.withKeys("postImage/" + post.getId());
amazonS3.deleteObjects(deleteObjectsRequest);
}
}
1. 사용자와 게시글의 존재 여부를 확인한다.
2. 해당 게시글에 연관된 이미지가 DB에 존재하는지 확인한다.
3. 게시글의 제목, 내용, 타입을 업데이트한다.
4. 이미지 처리 로직을 수행한다.전달되어온 이미지 DB에 기존 이미지 존재 여부 처리 방식
전달되어온 이미지 | DB에 기존 이미지 존재 여부 | 처리 방식 |
O | O | 기존 이미지 삭제 & 새 이미지 업로드 |
O | X | 새 이미지 업로드 |
X | O | 기존 이미지 삭제 |
X | X | Skip |
- 새로운 이미지 파일이 있는 경우
- 기존 이미지가 있었다면 모두 삭제한다.
- 새로운 이미지를 업로드한다.
- 새로운 이미지 파일이 없고, 기존 이미지가 있는 경우
- 기존 이미지를 모두 삭제한다. (전달받은 새로운 이미지가 없으면 삭제 요청으로 판단)
5. 수정된 게시글의 ID를 반환한다.
- `@Transactional` 어노테이션을 사용하기 때문에, 어느 한 단계에서라도 실패하면 전체 작업이 롤백된다.
- 게시글 수정에 대한 이미지 로직을 어떻게 할지 고민하다가, 현재 게시글의 경우 이미지는 5개까지 허용하기 때문에 기존 이미지를 모두 삭제하는 것이 리소스가 많이 들지 않을 것이라고 판단하여 위와 같이 구현했다.
- `deleteImages` 메서드는 DB와 AWS S3에서 이미지를 삭제하는 작업을 수행한다.이 메서드는 `deletePost` (게시글 삭졔) 메서드에도 존재하는 로직이기 때문에 `deleteImages` 메서드로 분리하고 `deletePost` 를 일부 수정했다.
@Transactional
public Boolean deletePost(Long userId, Long postId) {
userRepository.findById(userId).orElseThrow(() -> new CommonException(ErrorCode.NOT_FOUND_USER));
Post post =
postRepository.findById(postId).orElseThrow(() -> new CommonException(ErrorCode.NOT_FOUND_POST));
try {
// DB, S3 이미지 삭제 (수정됨)
deleteImages(post);
// 게시물 삭제
postRepository.delete(post);
return true;
} catch (Exception e) {
throw new CommonException(ErrorCode.POST_ERROR);
}
}
5. API 응답 예시
요청 URL
[PUT] http://cheongha.site/api/v1/posts/258
Response Body
{
"data": {
"postId": 258
},
"error": null
}
반응형
'Projects > 청하-청년을 위한 커뮤니티 서비스' 카테고리의 다른 글
[청하] 14. Spring Boot 파일 업로드 용량 문제 해결 (0) | 2024.10.10 |
---|---|
[청하] 13. Spring Boot 서버 배포 시 타임존 문제 해결 (feat. @PostConstruct) (2) | 2024.10.09 |
[청하] 11. 게시글 삭제 기능 구현 (feat. NCP Object Storage 이미지 삭제) (1) | 2024.10.09 |
[청하] 10. 게시글 리스트 조회 기능 구현 (feat. 정적 팩토리 메서드) (5) | 2024.10.08 |
[청하] 9. 게시글 createDate 포멧 변경 (feat. DateTimeFormatter) (1) | 2024.10.08 |
댓글