BoardResponse
package com.tenco.blog_jpa_step4.board;
import com.tenco.blog_jpa_step4.reply.Reply;
import com.tenco.blog_jpa_step4.user.User;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import java.util.ArrayList;
import java.util.List;
public class BoardResponse {
@Getter
@Setter
public static class DTO {
private int id;
private String title;
private String content;
// 게시글의 기본 정보를 담은 생성자
public DTO(Board board) {
this.id = board.getId();
this.title = board.getTitle();
this.content = board.getContent();
}
}
// 게시글 상세보기 화면을 위한 DTO 클래스
@Getter
@Setter
@ToString
public static class DetailDTO {
private int id;
private String title;
private String content;
private int userId;
private String username; // 게시글 작성자 이름
private boolean isOwner; // 현재 사용자가 작성자인지 여부
private List<ReplyDTO> replies = new ArrayList<>(); // 댓글 목록
// 게시글 상세 정보를 담은 생성자
public DetailDTO(Board board, User sessionUser) {
this.id = board.getId();
this.title = board.getTitle();
this.content = board.getContent();
this.userId = board.getUser().getId();
this.username = board.getUser().getUsername(); // join 해서 가져왔음
this.isOwner = false;
if(sessionUser != null){
if(sessionUser.getId() == userId) isOwner = true;
}
// 게시글의 댓글 목록을 ReplyDTO로 변환하여 설정
for (Reply reply : board.getReplies()) {
this.replies.add(new ReplyDTO(reply, sessionUser));
}
//this.replies = board.getReplies().stream().map(reply -> new ReplyDTO(reply, sessionUser)).toList();
}
@Getter
@Setter
public static class ReplyDTO {
private int id;
private String comment;
private int userId; // 댓글 작성자 아이디
private String username; // 댓글 작성자 이름
private boolean isOwner; // 현재 사용자가 댓글 작성자인지 여부
// 댓글의 기본 정보를 담은 생성자
public ReplyDTO(Reply reply, User sessionUser) {
this.id = reply.getId(); // lazy loading 발동
this.comment = reply.getComment();
this.userId = reply.getUser().getId();
this.username = reply.getUser().getUsername(); // lazy loading 발동 (in query)
this.isOwner = sessionUser != null && sessionUser.getId().equals(userId);
}
}
}
// 게시글 목록보기 화면을 위한 DTO 클래스
@Getter
@Setter
public static class ListDTO {
private int id;
private String title;
// 게시글의 기본 정보를 담은 생성자
public ListDTO(Board board) {
this.id = board.getId();
this.title = board.getTitle();
}
}
}
BoardController
package com.tenco.blog_jpa_step4.board;
import com.auth0.jwt.exceptions.TokenExpiredException;
import com.tenco.blog_jpa_step4.commom.utils.ApiUtil;
import com.tenco.blog_jpa_step4.commom.utils.Define;
import com.tenco.blog_jpa_step4.commom.utils.JwtUtil;
import com.tenco.blog_jpa_step4.user.User;
import jakarta.servlet.http.HttpServletRequest;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.List;
/**
* BoardController 블로그 게시글과 관련된 HTTP 요청을 처리하는 REST 컨트롤러 클래스입니다.
*/
@Slf4j
@RequiredArgsConstructor
@RestController // @Controller -> @RestController로 변경
public class BoardController {
private final BoardService boardService; // BoardService 주입
/**
* 게시글 목록 조회 처리 메서드
* 요청 주소: **GET <http://localhost:8080/api/boards**>
*
* @return 게시글 목록 DTO 리스트
*/
@GetMapping({"/boards", "/"})
public ResponseEntity<List<BoardResponse.ListDTO>> getAllBoards() {
List<BoardResponse.ListDTO> boardList = boardService.getAllBoards();
return ResponseEntity.ok(boardList);
}
/**
* 게시글 상세보기 처리 메서드
* 요청 주소: **GET <http://localhost:8080/boards/{id}**>
*
* @param id 게시글의 ID
* @param request HTTP 요청 객체
* @return 게시글 상세보기 DTO
*/
@GetMapping("/boards/{id}")
public ResponseEntity<ApiUtil<BoardResponse.DetailDTO>> getBoardDetail(@PathVariable(name = "id") Integer id, HttpServletRequest request) {
User sessionUser = null;
// api 경로가 아니기 때문에 JWT 확인 해야 함
String authorizationHeader = request.getHeader(Define.AUTHORIZATION);
if (authorizationHeader != null && authorizationHeader.startsWith(Define.BEARER)) {
String token = authorizationHeader.replace(Define.BEARER, "");
try {
sessionUser = JwtUtil.verify(token);
} catch (TokenExpiredException e) {
return ResponseEntity.status(401).body(new ApiUtil<>(401, "토큰이 만료되었습니다. 다시 로그인해주세요."));
} catch (Exception e) {
return ResponseEntity.status(401).body(new ApiUtil<>(401, "유효하지 않은 토큰입니다."));
}
}
// 게시글 상세보기 로직
BoardResponse.DetailDTO boardDetail = boardService.getBoardDetails(id, sessionUser);
return ResponseEntity.ok(new ApiUtil<>(boardDetail));
}
/**
* 게시글 작성 처리 메서드
* 요청 주소: POST <http://localhost:8080/api/boards>
* @param dto 게시글 작성 요청 DTO
* @return 작성된 게시글 DTO
*/
@PostMapping("/api/boards")
public ResponseEntity<?> createBoard(@RequestBody BoardRequest.SaveDTO reqDTO, HttpServletRequest request) {
User sessionUser = (User) request.getAttribute(Define.SESSION_USER); // 인터셉터에서 설정한 사용자 정보 가져오기
// 게시글 작성 서비스 호출
BoardResponse.DTO savedBoard = boardService.createBoard(reqDTO, sessionUser);
return ResponseEntity.ok(new ApiUtil<>(savedBoard));
}
/**
* 게시글 수정 처리 메서드
* 요청 주소: PUT <http://localhost:8080/api/boards/{id}>
* @param id 수정할 게시글의 ID
* @param updateDTO 수정된 데이터를 담은 DTO
* @return 수정된 게시글 DTO
*/
@PutMapping("/api/boards/{id}")
public ResponseEntity<?> updateBoard(@PathVariable(name = "id") Integer id,
BoardRequest.UpdateDTO updateDTO, HttpServletRequest request) {
User sessionUser = (User) request.getAttribute(Define.SESSION_USER); // 인터셉터에서 설정한 사용자 정보 가져오기
// 인증 사용자 여부 검사
if (sessionUser == null) {
return ResponseEntity.status(401).build(); // 인증되지 않은 경우 401 반환
}
// 게시글 수정 서비스 호출
BoardResponse.DTO updatedBoard = boardService.updateBoard(id, sessionUser.getId(), updateDTO);
return ResponseEntity.ok(new ApiUtil<>(updatedBoard));
}
/**
* 게시글 삭제 처리 메서드
* 요청 주소: **DELETE <http://localhost:8080/api/boards/{id}**>
* @param id 삭제할 게시글의 ID
* @return 성공적으로 삭제된 경우 204 No Content 응답
*/
@DeleteMapping("/api/boards/{id}")
public ResponseEntity<?> deleteBoard(@PathVariable(name = "id") Integer id, HttpServletRequest request) {
User sessionUser = (User) request.getAttribute(Define.SESSION_USER); // 인터셉터에서 설정한 사용자 정보 가져오기
// 세션 유효성 검증
if (sessionUser == null) {
return ResponseEntity.status(401).build(); // 인증되지 않은 경우 401 반환
}
// 게시글 삭제 서비스 호출
boardService.deleteBoard(id, sessionUser.getId());
// 삭제 후 응답
return ResponseEntity.ok(new ApiUtil<>(null));
}
}
BoardService
package com.tenco.blog_jpa_step4.board;
import com.tenco.blog_jpa_step4.commom.errors.Exception403;
import com.tenco.blog_jpa_step4.commom.errors.Exception404;
import com.tenco.blog_jpa_step4.user.User;
import jakarta.transaction.Transactional;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Sort;
import org.springframework.stereotype.Service;
import java.util.List;
@RequiredArgsConstructor
@Service // 서비스 계층으로 등록
public class BoardService {
private final BoardJPARepository boardJPARepository;
/**
* 게시글 목록 조회 서비스
* @return 게시글 목록의 DTO 리스트
*/
public List<BoardResponse.ListDTO> getAllBoards() {
Sort sort = Sort.by(Sort.Direction.DESC, "id");
List<Board> boards = boardJPARepository.findAll(sort);
return boards.stream().map(BoardResponse.ListDTO::new).toList();
}
/**
* 게시글 상세 조회 서비스
* @param boardId 조회할 게시글의 ID
* @param sessionUser 현재 세션 사용자 정보
* @return 게시글 상세 정보의 DTO
*/
// 메서드 종료까지 영속성 컨텍스 즉 connection 열어 있음
// @Transactional 없는 경우 오류 발생 (LazyInitializationException)
@Transactional
public BoardResponse.DetailDTO getBoardDetails(int boardId, User sessionUser) {
Board board = boardJPARepository.findByIdJoinUser(boardId)
.orElseThrow(() -> new Exception404("게시글을 찾을 수 없습니다"));
BoardResponse.DetailDTO boardDetail = new BoardResponse.DetailDTO(board, sessionUser);
System.out.println(boardDetail.toString());
return boardDetail;
}
/**
* 게시글 작성 서비스
* @param reqDTO 게시글 작성 요청 DTO
* @param sessionUser 현재 세션 사용자 정보
* @return 작성된 게시글의 DTO
*/
@Transactional
public BoardResponse.DTO createBoard(BoardRequest.SaveDTO reqDTO, User sessionUser) {
Board savedBoard = boardJPARepository.save(reqDTO.toEntity(sessionUser));
return new BoardResponse.DTO(savedBoard);
}
/**
* 게시글 수정 서비스
* @param boardId 수정할 게시글의 ID
* @param sessionUserId 현재 세션 사용자 ID
* @param reqDTO 수정된 게시글 정보의 DTO
* @return 수정된 게시글의 DTO
* @throws Exception404 게시글을 찾을 수 없는 경우 발생
* @throws Exception403 권한이 없는 사용자가 수정하려는 경우 발생
*/
@Transactional
public BoardResponse.DTO updateBoard(int boardId, int sessionUserId, BoardRequest.UpdateDTO reqDTO) {
Board board = boardJPARepository.findById(boardId)
.orElseThrow(() -> new Exception404("게시글을 찾을 수 없습니다"));
if (sessionUserId != board.getUser().getId()) {
throw new Exception403("게시글을 수정할 권한이 없습니다");
}
board.setTitle(reqDTO.getTitle());
board.setContent(reqDTO.getContent());
return new BoardResponse.DTO(board);
}
/**
* 게시글 삭제 서비스
* @param boardId 삭제할 게시글의 ID
* @param sessionUserId 현재 세션 사용자 ID
* @throws Exception404 게시글을 찾을 수 없는 경우 발생
* @throws Exception403 권한이 없는 사용자가 삭제하려는 경우 발생
*/
@Transactional
public void deleteBoard(int boardId, int sessionUserId) {
Board board = boardJPARepository.findById(boardId)
.orElseThrow(() -> new Exception404("게시글을 찾을 수 없습니다"));
if (sessionUserId != board.getUser().getId()) {
throw new Exception403("게시글을 삭제할 권한이 없습니다");
}
boardJPARepository.deleteById(boardId);
}
}
BoardRepository
package com.tenco.blog_jpa_step4.board;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import java.util.Optional;
// BoardJPARepository는 Board 엔티티에 대한 CRUD 기능을 제공한다.
public interface BoardJPARepository extends JpaRepository<Board, Integer> {
// JPQL - Fetch JOIN 사용
// 커스텀 쿼리 메서드: Board와 User를 조인하여 특정 Board 조회
@Query("select b from Board b join fetch b.user u where b.id = :id")
Optional<Board> findByIdJoinUser(@Param("id") int id);
}
'Spring boot' 카테고리의 다른 글
application.properties와 application.yml의 차이점 (0) | 2025.01.09 |
---|---|
Session이란 무엇인가 (0) | 2025.01.07 |
User JWT 적용 (0) | 2024.11.07 |
JWT 인터셉터 적용 (1) | 2024.11.07 |
JDBC란 뭘까? - 1 (1) | 2024.11.07 |