게시글 등록 기능에 이어서 추가하는 기능은 “게시글 상세조회” 이다.
1. 프로젝트 구조
src/main/java/com/example/withpeace/
│
├── domain/ # 도메인 모델 (엔티티)
│ ├── Image.java # 이미지 엔티티
│ └── Post.java # 게시글 엔티티
│
├── repository/ # 데이터 접근 계층
│ ├── UserRepository.java # 사용자 레포지토리
│ ├── ImageRepository.java # 이미지 레포지토리
│ └── PostRepository.java # 게시글 레포지토리
│
├── dto/ # 데이터 전송 객체
│ └── response/
│ └── PostDetailResponseDto.java # 게시글 상세조회 응답 DTO
│
├── controller/ # 컨트롤러 계층
│ └── PostController.java # 게시글 관련 API 엔드포인트
│
├── service/ # 비즈니스 로직 계층
│ └── PostService.java # 게시글 관련 비즈니스 로직
│
└── util/ # 유틸리티 클래스
└── TimeFormatter.java # 시간 포맷팅 유틸리티
2. Post 엔티티 수정
@Entity
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@DynamicUpdate
@Table(name = "posts")
public class Post {
// ... (다른 필드 생략)
@Column(name = "create_date", nullable = false)
// private LocalDate createDate; // 변경 전
private LocalDateTime createDate; // 변경 후
@Builder
public Post(User writer, String title, String content, ETopic type) {
this.writer = writer;
this.title = title;
this.content = content;
this.type = type;
// this.createDate = LocalDate.now(); // 변경 전
this.createDate = LocalDateTime.now(); // 변경 후
}
}
createDate 필드의 타입을 LocalDate에서 LocalDateTime으로 변경했다. 게시글 업로드 시간을 정확하게 기록하고, 상세조회 시 시간 포맷팅을 위해 변경했다.
LocalDate VS LocalDateTime
- LocalDate
- 날짜만 저장한다. (년, 월, 일)
- ex) “2024-02-15”
- LocalDateTime
- 날짜와 시간을 저장한다. (년, 월, 일, 시, 분, 초, 나노초)
- ex) "2024-02-15T14:00”
- 시간 기반 연산과 비교가 가능하다.
3. DTO 구현
@Builder
public record PostDetailResponseDto(
Long postId,
Long userId,
String nickname,
String profileImageUrl,
String title,
String content,
ETopic type,
String createDate,
List<String> postImageUrls) {
}
`PostDetailResponseDto` 는 게시글 상세조회의 응답을 위한 데이터 전송 객체이다.
해당 게시글을 작성한 사용자의 정보와 게시글의 정보, 게시글 관련 이미지 URL을 포함한다.
4. 레포지토리 구현
@Repository
public interface ImageRepository extends JpaRepository<Image, Long> {
@Query("SELECT i.url FROM Image i WHERE i.post = :post")
List<String> findUrlsByPost(Post post);
}
`findUrlsByPost()` 는 특정 게시글과 연관된 모든 이미지 URL을 조회하는 커스텀 쿼리 메서드이다.
5. 컨트롤러 구현
@RestController
@RequiredArgsConstructor
@RequestMapping("/api/v1/posts")
public class PostController {
private final PostService postService;
// ... (게시글 등록 관련 메서드 생략)
// 게시글 상세조회
@GetMapping("/{postId}")
public ResponseDto<PostDetailResponseDto> getPostDetail(@UserId Long userId, @PathVariable Long postId) {
return ResponseDto.ok(postService.getPostDetail(userId, postId));
}
}
인증된 사용자 ID와 경로변수로 게시글 ID를 받아 서비스 계층에 넘긴다.
6. 서비스 구현
@Service
@RequiredArgsConstructor
public class PostService {
// ... (필드 선언 생략)
// ... (게시글 등록 관련 메서드 생략)
@Transactional
public PostDetailResponseDto getPostDetail(Long userId, Long postId) {
User user =
userRepository.findById(userId).orElseThrow(() -> new CommonException(ErrorCode.NOT_FOUND_USER));
Post post =
postRepository.findById(postId).orElseThrow(() -> new CommonException(ErrorCode.NOT_FOUND_POST));
String formatterDate = TimeFormatter.timeFormat(post.getCreateDate());
List<String> postImageUrls = Optional.ofNullable(imageRepository.findUrlsByPost(post))
.orElse(Collections.emptyList());
PostDetailResponseDto postDetailResponseDto =
PostDetailResponseDto.builder()
.postId(postId)
.userId(post.getWriter().getId())
.nickname(user.getNickname())
.profileImageUrl(user.getProfileImage())
.title(post.getTitle())
.content(post.getContent())
.type(post.getType())
.createDate(formatterDate)
.postImageUrls(postImageUrls)
.build();
return postDetailResponseDto;
}
}
사용자와 게시글 정보를 조회하고, 만약 찾지 못한 경우 에러를 반환한다.
게시글 생성 시간(createDate)를 포맷팅하고, 연관 이미지 URL을 가져와 DTO를 구성한다.
List<String> postImageUrls = Optional.ofNullable(imageRepository.findUrlsByPostId(post))
.orElse(Collections.emptyList());
연관 이미지 URL이 없으면 빈 리스트를 생성한다.
7. 유틸리티 클래스 구현
@Component
public class TimeFormatter {
public static String timeFormat(LocalDateTime time) {
LocalDateTime now = LocalDateTime.now();
Duration duration = Duration.between(time, now);
if(duration.getSeconds() < 60) {
return duration.getSeconds() + "초 전";
} else if(duration.toMinutes() < 60) {
return duration.toMinutes() + "분 전";
} else if(duration.toHours() < 24) {
return duration.toHours() + "시간 전";
} else if(duration.toDays() < 7) {
return duration.toDays() + "일 전";
} else {
if (time.getYear() != now.getYear()) {
return time.format(DateTimeFormatter.ofPattern("yyyy년 M월 d일"));
} else {
return time.format(DateTimeFormatter.ofPattern("M월 d일"));
}
}
}
}
TimeFormatter는 게시글 생성 시간(createDate)과 현재 시간의 차이에 따라 적절한 형식의 문자열을 반환한다.
- 1분 미만: “~초 전”
- 1시간 미만: “~분 전”
- 24시간 미만: “~시간 전”
- 7일 미만: “~일 전”
- 올해 아님: "yyyy년 M월 d일"
- 올해: "M월 d일"
반응형
'Projects > 청하-청년을 위한 커뮤니티 서비스' 카테고리의 다른 글
[청하] 10. 게시글 리스트 조회 기능 구현 (feat. 정적 팩토리 메서드) (5) | 2024.10.08 |
---|---|
[청하] 9. 게시글 createDate 포멧 변경 (feat. DateTimeFormatter) (1) | 2024.10.08 |
[청하] 7. 게시글 등록 기능 - 인가 권한 설정 (0) | 2024.10.07 |
[청하] 6. 게시글 등록 기능 - @ModelAttribute를 이용한 form-data 처리 개선 (0) | 2024.10.07 |
[청하] 5. 게시글 등록 기능 - @RequestPart를 이용한 JSON 데이터와 파일 데이터 분리 (0) | 2024.10.07 |
댓글