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);
}
UserController
package com.tenco.blog_v3.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.commom.utils.ApiUtil;
import com.tenco.blog_jpa_step4.commom.utils.Define;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpSession;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
/**
* UserController 사용자(User)와 관련된 HTTP 요청을 처리하는 컨트롤러 계층입니다.
*/
@Slf4j
@RestController
@RequiredArgsConstructor
public class UserController {
private final UserService userService; // UserService 주입
// 회원 정보 조회 -- > 추후 api/users/{id} 로 수정
@GetMapping("/api/users/{id}")
public ResponseEntity<ApiUtil<UserResponse.DTO>> userinfo(@PathVariable(name = "id") Integer id,
HttpServletRequest request) {
// 인터셉터에서 설정한 사용자 정보를 가져오기
User sessionUser = (User) request.getAttribute(Define.SESSION_USER);
if (sessionUser == null) {
throw new Exception404("사용자를 찾을 수 없습니다."); // 사용자가 존재하지 않는 경우 예외 던지기
}
UserResponse.DTO resDTO = userService.findUserById(sessionUser.getId());
return ResponseEntity.ok(new ApiUtil<>(resDTO));
}
/**
* 사용자 정보 수정 요청 처리
*
* @param id 수정할 사용자 ID
* @param reqDTO 수정된 사용자 정보 DTO
* @return 수정된 사용자 정보의 DTO
*/
@PutMapping("/api/users/{id}")
public ResponseEntity<ApiUtil<UserResponse.DTO>> updateUser(@PathVariable int id,
@RequestBody UserRequest.UpdateDTO reqDTO,
HttpServletRequest request) {
// 인터셉터에서 설정한 사용자 정보를 가져오기
User sessionUser = (User) request.getAttribute(Define.SESSION_USER);
if (sessionUser == null) {
throw new Exception404("사용자를 찾을 수 없습니다."); // 사용자가 존재하지 않는 경우 예외 던지기
}
if (sessionUser.getId() != id) {
throw new Exception403("해당 사용자를 수정할 권한이 없습니다."); // 권한 없음 예외 던지기
}
UserResponse.DTO resDTO = userService.updateUser(id, reqDTO, sessionUser);
return ResponseEntity.ok(new ApiUtil<>(resDTO));
}
@PostMapping("/join")
public ResponseEntity<ApiUtil<UserResponse.DTO>> join(@RequestBody UserRequest.JoinDTO reqDTO) {
UserResponse.DTO resDTO = userService.signUp(reqDTO);
return ResponseEntity.ok(new ApiUtil<>(resDTO));
}
@PostMapping("/login")
public ResponseEntity<?> login(@RequestBody UserRequest.LoginDTO reqDTO) {
String jwt = userService.signIn(reqDTO);
return ResponseEntity.ok()
// 반드시 주의!!! Bearer 문자열 뒤에 반드시 한칸에 공백을 넣어 주세요 ~~
.header("Authorization", "Bearer " + jwt)
.body(new ApiUtil<>(null));
}
@GetMapping("/logout")
public ResponseEntity<?> logout(HttpSession session) {
session.invalidate();
return ResponseEntity.ok(new ApiUtil<>(null));
}
}
BoardService
package com.tenco.blog_v3.board;
import com.tenco.blog_jpa_step4.commom.errors.Exception400;
import com.tenco.blog_jpa_step4.commom.errors.Exception401;
import com.tenco.blog_jpa_step4.commom.errors.Exception404;
import com.tenco.blog_jpa_step4.commom.utils.JwtUtil;
import jakarta.transaction.Transactional;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import java.util.Optional;
@RequiredArgsConstructor
@Service // 서비스 계층으로 등록 및 IoC
public class UserService {
private final UserJPARepository userJPARepository;
/**
* 회원 정보 조회 서비스
*
* @param id 조회할 사용자 ID
* @return 조회된 사용자 객체의 DTO
* @throws Exception404 사용자를 찾을 수 없는 경우 발생
*/
public UserResponse.DTO findUserById(int id){
User user = userJPARepository.findById(id)
.orElseThrow(() -> new Exception404("회원정보를 찾을 수 없습니다"));
return new UserResponse.DTO(user);
}
/**
* 회원가입 서비스
*
* @param reqDTO 회원가입 요청 DTO
* @return 회원가입 완료된 사용자 정보의 DTO
* @throws Exception400 중복된 유저네임인 경우 발생
*/
@Transactional // 트랜잭션 관리
public UserResponse.DTO signUp(UserRequest.JoinDTO reqDTO) {
// 1. 유저네임 중복검사 (DB 연결이 필요한 것은 Controller 에서 작성하지 말자)
Optional<User> userOP = userJPARepository.findByUsername(reqDTO.getUsername());
if (userOP.isPresent()) {
throw new Exception400("중복된 유저네임입니다");
}
// 2. 회원가입
User savedUser = userJPARepository.save(reqDTO.toEntity());
return new UserResponse.DTO(savedUser);
}
/**
* 로그인 서비스
*
* @throws Exception401 인증 실패 시 발생
*/
// 리턴 타입 변경
public String signIn(UserRequest.LoginDTO reqDTO) {
User user = userJPARepository.findByUsernameAndPassword(reqDTO.getUsername(), reqDTO.getPassword())
.orElseThrow(() -> new Exception401("인증되지 않았습니다"));
// session.setAttribute("sessionUser", user); // 세션에 사용자 정보 저장
// jwt 문자열 반환 처리
return JwtUtil.create(user); // 로그인 시 이메일 정보 제외
}
/**
* 회원 정보 수정 서비스
*
* @param id 수정할 사용자 ID
* @param reqDTO 수정된 사용자 정보 DTO
* @return 수정된 사용자 객체의 DTO
* @throws Exception404 사용자를 찾을 수 없는 경우 발생
*/
@Transactional // 트랜잭션 관리
public UserResponse.DTO updateUser(int id, UserRequest.UpdateDTO reqDTO, User sessionUser) {
// 1. 사용자 조회 및 예외 처리
User user = userJPARepository.findById(sessionUser.getId())
.orElseThrow(() -> new Exception404("회원정보를 찾을 수 없습니다"));
// 2. 사용자 정보 수정
user.setPassword(reqDTO.getPassword());
user.setEmail(reqDTO.getEmail());
// 더티 체킹을 통해 변경 사항이 자동으로 반영됩니다.
return new UserResponse.DTO(user);
}
}
'Flutter' 카테고리의 다른 글
플러터 기본기 다지기 - 위젯트리, 엘리먼트트리, 렌더트리 란 뭘까? (0) | 2024.11.22 |
---|---|
플러터 기본기 다지기 - Basic Widget 살펴 보기 (0) | 2024.11.21 |
서비스의 인증과 권한 부여 (0) | 2024.11.20 |
플러터 기본기 다지기 - Flutter 프로젝트 구조 이해하기 (1) | 2024.11.20 |
플러터 기본기 다지기 - 나만의 Flutter 위젯 분류 (0) | 2024.11.19 |