RestAPI 컨트롤러 요청과 응답

coding S's avatar
Mar 24, 2024
RestAPI 컨트롤러 요청과 응답

[ 시작하기 전! @RestController 처리 모두 다 해라!! ]

notion image
💡
이거 처리 안 하면 static resource (뷰) 찾을 수 없다는 에러가 뜸
 

1. 요청 BODY 수정 (json으로 변경)

요청 body에 @RequestBody 만 붙이면 됨 -> 그럼 전부 json 요청으로 바뀐다 어떤 건 폼으로 받고, 어떤건 json으로 받고... 이러는 건 좋지 않다. 하나로 통일하자~
notion image
💡
바디 데이터 -> post, put 요청에 전부 @RequestBody를 붙이고 다니자
 

2. 응답 body 수정

응답은 return에 ResponseEntity 처리. 지금은 응답 바디에 form이나 redirect 처리를 한 상태. (응답은 상태코드 200만 날려주면 된다.)

ok = 200

notion image
notion image
1. ResponseEntity.ok(new ApiUtil(newSessionUser)); 2. new ResponseEntity<>(new ApiUtil(newSessionUser), HttpStatus.OK); 이 2개는 같은 코드이나... ok라고 쓰는게 더 간결하고 보기 좋다!! ok 자체가 200을 뜻하니까! 그러니 1번 코드를 사용하자!

UserService 응답 body 수정

[ 회원가입 ]

notion image
💡
회원가입 하고 유저정보를 안 돌려주면 안됨 세이브된 데이터를 응답해줘야함 < 규칙!!
 

[ 로그인 ]

notion image
💡
row가 insert되거나 update 된 게 없으니 돌려줄 게 없다 200, 성공, null로 응답이 될 것임
notion image

[ 로그인 실패 시 ]

notion image

[ 로그아웃 ]

notion image
 

User 전체 코드

[ UserService ]

package shop.mtcoding.blog.user; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import shop.mtcoding.blog._core.errs.exception.Exception400; import shop.mtcoding.blog._core.errs.exception.Exception401; import shop.mtcoding.blog._core.errs.exception.Exception404; import java.util.Optional; @RequiredArgsConstructor @Service public class UserService { private final UserJPARepository userJPARepository; @Transactional public User 회원수정(int id, UserRequest.UpdateDTO requestDTO) { User user = userJPARepository.findById(id) .orElseThrow(() -> new Exception404("회원정보를 찾을 수 없습니다.")); user.setPassword(requestDTO.getPassword()); user.setEmail(requestDTO.getEmail()); return user; } //더티체킹 public User 회원조회(int id) { User user = userJPARepository.findById(id) .orElseThrow(() -> new Exception404("회원정보를 찾을 수 없습니다.")); return user; } //조회라 트랜젝션 안 붙여도 됨! public User 로그인(UserRequest.LoginDTO requestDTO) { //나중에 해시 비교하는 이런 코드 여기에 들어옴 User sessionUser = userJPARepository.findByUsernameAndPassword(requestDTO.getUsername(), requestDTO.getPassword()) .orElseThrow(() -> new Exception401("인증되지 않았습니다")); return sessionUser; } @Transactional public User 회원가입(UserRequest.JoinDTO requestDTO) { // 1. 유효성 검사 -> 컨트롤러 책임 x // 2. 유저네임 중복검사 (서비스 체크) - DB연결 필요 Optional<User> userOP = userJPARepository.findByUsername(requestDTO.getUsername()); //isPresent가 있으면 비정상 if (userOP.isPresent()) { throw new Exception400("중복된 유저네임입니다."); } return userJPARepository.save(requestDTO.toEntity()); } }
 

[ UserController ]

package shop.mtcoding.blog.user; import jakarta.persistence.NoResultException; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpSession; import lombok.RequiredArgsConstructor; import org.springframework.dao.DataIntegrityViolationException; import org.springframework.dao.EmptyResultDataAccessException; import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.*; import shop.mtcoding.blog._core.errs.exception.Exception400; import shop.mtcoding.blog._core.errs.exception.Exception401; import shop.mtcoding.blog._core.utils.ApiUtil; import shop.mtcoding.blog.board.Board; import shop.mtcoding.blog.board.BoardRepository; @RequiredArgsConstructor @RestController public class UserController { private final UserService userService; private final HttpSession session; // TODO: 회원정보 조회 API 필요 -> @GetMapping("/api/user/{id}") @GetMapping("/api/user/{id}") public ResponseEntity<?> userinfo(@PathVariable Integer id) { User user = userService.회원조회(id); return ResponseEntity.ok(new ApiUtil(user)); } @PutMapping("/api/users/{id}") public ResponseEntity<?> update(@PathVariable Integer id, @RequestBody UserRequest.UpdateDTO requestDTO) { User sessionUser = (User) session.getAttribute("sessionUser"); User newSessionUser = userService.회원수정(sessionUser.getId(), requestDTO); session.setAttribute("sessionUser", newSessionUser); return ResponseEntity.ok(new ApiUtil(newSessionUser)); } @PostMapping("/join") public ResponseEntity<?> join(@RequestBody UserRequest.JoinDTO requestDTO) { User user = userService.회원가입(requestDTO); return ResponseEntity.ok(new ApiUtil(user)); } @PostMapping("/login") public ResponseEntity<?> login(@RequestBody UserRequest.LoginDTO requestDTO) { User sessionUser = userService.로그인(requestDTO); session.setAttribute("sessionUser", sessionUser); return ResponseEntity.ok(new ApiUtil(null)); } @GetMapping("/logout") public ResponseEntity<?> logout() { session.invalidate(); return ResponseEntity.ok(new ApiUtil(null)); } }
 

BoardService / BoardController - 글쓰기만 예시

[ 글쓰기 ]

@Transactional public Board 글쓰기(BoardRequest.SaveDTO requestDTO, User sessionUser) { Board board = boardJPARepository.save(requestDTO.toEntity(sessionUser)); return board; //나중에 board가 아니라 dto를 리턴해줘야함!! }
@PostMapping("/api/boards") public ResponseEntity<?> save(@RequestBody BoardRequest.SaveDTO requestDTO) { User sessionUser = (User) session.getAttribute("sessionUser"); //권한 체크는 생략 Board board = boardService.글쓰기(requestDTO, sessionUser); return ResponseEntity.ok(new ApiUtil(board)); }

return board를 한다고? 진짜 위험한 코드 (무한 순환 참조)

 

Board 전체 코드

[ BoardService ]

package shop.mtcoding.blog.board; import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Sort; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import shop.mtcoding.blog._core.errs.exception.Exception403; import shop.mtcoding.blog._core.errs.exception.Exception404; import shop.mtcoding.blog.user.User; import java.util.List; @RequiredArgsConstructor @Service public class BoardService { private final BoardJPARepository boardJPARepository; public Board 글조회(int boardId) { Board board = boardJPARepository.findById(boardId) .orElseThrow(() -> new Exception404("게시글을 찾을 수 없습니다")); //2. 권한 처리 // if (sessionUserId != board.getUser().getId()) { // throw new Exception403("게시글을 수정페이지로 이동 할 권한이 없습니다"); // } return board; } @Transactional public Board 글수정(int boardId, int sessionUserId, BoardRequest.UpdateDTO requestDTO) { //1. 더티체킹 하기 위해 조회하고 예외처리 Board board = boardJPARepository.findById(boardId) .orElseThrow(() -> new Exception404("게시글을 찾을 수 없습니다")); //2. 권한 처리 if (sessionUserId != board.getUser().getId()) { throw new Exception403("게시글을 수정할 권한이 없습니다"); } //3. 실제 글 수정 (더티체킹 함) board.setTitle(requestDTO.getTitle()); board.setContent(requestDTO.getContent()); return board; } @Transactional public Board 글쓰기(BoardRequest.SaveDTO requestDTO, User sessionUser) { Board board = boardJPARepository.save(requestDTO.toEntity(sessionUser)); return board; } @Transactional public void 글삭제(Integer boardId, Integer sessionUserId) { Board board = boardJPARepository.findById(boardId) .orElseThrow(() -> new Exception404("게시글을 찾을 수 없습니다.")); if (sessionUserId != board.getUser().getId()) { throw new Exception403("게시글 삭제 권한 없음!"); } boardJPARepository.deleteById(boardId); } public List<Board> 글목록조회() { Sort sort = Sort.by(Sort.Direction.DESC, "id"); return boardJPARepository.findAll(sort); } // board, isOwner public Board 글상세보기(int boardId, User sessionUser) { Board board = boardJPARepository.findByIdJoinUser(boardId) .orElseThrow(() -> new Exception404("게시글을 찾을 수 없습니다")); boolean isBoardOwner = false; if(sessionUser != null){ if(sessionUser.getId() == board.getUser().getId()){ isBoardOwner = true; } } board.setBoardOwner(isBoardOwner); board.getReplies().forEach(reply -> { boolean isReplyOwner = false; if(sessionUser != null){ if(reply.getUser().getId() == sessionUser.getId()){ isReplyOwner = true; } } reply.setReplyOwner(isReplyOwner); }); return board; } }
 

[ BoardController ]

package shop.mtcoding.blog.board; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpSession; import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Controller; import org.springframework.transaction.annotation.Transactional; import org.springframework.web.bind.annotation.*; import shop.mtcoding.blog._core.errs.exception.Exception403; import shop.mtcoding.blog._core.errs.exception.Exception404; import shop.mtcoding.blog._core.utils.ApiUtil; import shop.mtcoding.blog.user.User; import java.util.List; @RequiredArgsConstructor @RestController public class BoardController { private final BoardService boardService; private final HttpSession session; // TODO : 글 목록조회 API 필요 -> @GetMapping("/"); @GetMapping("/") public ResponseEntity<?> main() { List<Board> boardList = boardService.글목록조회(); return ResponseEntity.ok(new ApiUtil(boardList)); } // TODO : 글 상세보기 API 필요 -> @GetMapping("/api/boards/{id}/detail") @GetMapping("/api/boards/{id}/detail") public ResponseEntity<?> detail(@PathVariable Integer id) { User sessionUser = (User) session.getAttribute("sessionUser"); Board board = boardService.글상세보기(id, sessionUser); return ResponseEntity.ok(new ApiUtil(board)); } // TODO : 글 조회 API 필요 -> @GetMapping("/api/boards/{id}") // 글상세보기 -> 글, 유저 정보, 댓글까지 다 있음 +)동사 추가함 detail // 글조회 -> 글만 조회 하는거라 글 수정할 때 필요 @GetMapping("/api/boards/{id}") public ResponseEntity<?> findOne(@PathVariable Integer id) { Board board = boardService.글조회(id); return ResponseEntity.ok(new ApiUtil(board)); } @PutMapping("/api/boards/{id}") public ResponseEntity<?> update(@PathVariable Integer id, @RequestBody BoardRequest.UpdateDTO requestDTO) { User sessionUser = (User) session.getAttribute("sessionUser"); Board board = boardService.글수정(id, sessionUser.getId(), requestDTO); return ResponseEntity.ok(new ApiUtil(board)); } @DeleteMapping("/api/boards/{id}") public ResponseEntity<?> delete(@PathVariable Integer id) { User sessionUser = (User) session.getAttribute("sessionUser"); boardService.글삭제(id, sessionUser.getId()); return ResponseEntity.ok(new ApiUtil(null)); } }
 

Reply 전체 코드

[ ReplyController ]

package shop.mtcoding.blog.reply; import jakarta.servlet.http.HttpSession; import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import shop.mtcoding.blog._core.utils.ApiUtil; import shop.mtcoding.blog.user.User; @RequiredArgsConstructor @RestController public class ReplyController { private final ReplyService replyService; private final HttpSession session; @DeleteMapping("/api/replies/{id}") public ResponseEntity<?> delete(@PathVariable Integer id) { User sessionUser = (User) session.getAttribute("sessionUser"); replyService.댓글삭제(id, sessionUser.getId()); return ResponseEntity.ok(new ApiUtil(null)); } @PostMapping("/api/replies") public ResponseEntity<?> save(@RequestBody ReplyRequest.SaveDTO requestDTO) { User sessionUser = (User) session.getAttribute("sessionUser"); Reply reply = replyService.댓글쓰기(requestDTO, sessionUser); return ResponseEntity.ok(new ApiUtil(reply)); } }
💡
댓글쓰기는 작성 된 댓글을 돌려줘야함. insert니까 return 바디 값이 들어감!
 

[ ReplyService ]

package shop.mtcoding.blog.reply; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import shop.mtcoding.blog._core.errs.exception.Exception403; import shop.mtcoding.blog._core.errs.exception.Exception404; import shop.mtcoding.blog.board.Board; import shop.mtcoding.blog.board.BoardJPARepository; import shop.mtcoding.blog.user.User; @RequiredArgsConstructor @Service public class ReplyService { private final ReplyJPARepository replyJPARepository; private final BoardJPARepository boardJPARepository; @Transactional public Reply 댓글쓰기(ReplyRequest.SaveDTO requestDTO, User sessionUser) { Board board = boardJPARepository.findById(requestDTO.getBoardId()) .orElseThrow(() -> new Exception404("없는 게시글에 댓글을 작성할 수 없어요")); Reply reply = requestDTO.toEntity(sessionUser, board); return replyJPARepository.save(reply); } @Transactional public void 댓글삭제(int replyId, int sessionUserId) { Reply reply = replyJPARepository.findById(replyId) .orElseThrow(() -> new Exception404("없는 댓글을 삭제할 수 없어요")); if(reply.getUser().getId() != sessionUserId){ throw new Exception403("댓글을 삭제할 권한이 없어요"); } replyJPARepository.deleteById(replyId); } }
 

나머지 API (TODO 처리한 것들) 만들자

[ UserController ]

notion image
@RequiredArgsConstructor @RestController public class UserController { private final UserService userService; private final HttpSession session; // TODO: 회원정보 조회 API 필요 -> @GetMapping("/api/users/{id}") @GetMapping("/api/users/{id}") public ResponseEntity<?> userinfo(@PathVariable Integer id){ User user = userService.회원조회(id); return ResponseEntity.ok(new ApiUtil(user)); }
💡
post, put이 아닌데 왜 돌려줘요> get 요청했으니까. 달라고 했으니까요 겟 요청은 원래 요청받은 것을 돌려줘야 함. 포스트, 풋 요청의 경우 새로운 것이 입력되어서 새로운 행이 추가되거나 원래 있던 내용이 수정되어서 행이 변경되는 것이라 무엇을 돌려줄 필요가 없을 수 있으나 프론트엔드가 궁금해할 수 있어서 돌려주라고 함. 정리하자면 겟 요청은 뭔가 달라고 한 것이니라 뭘 돌려주는 것이 인지상정
 

[ BoardController ]

notion image
@RequiredArgsConstructor @RestController public class BoardController { private final BoardService boardService; private final HttpSession session; // TODO : 글 목록조회 API 필요 -> @GetMapping("/"); @GetMapping("/") public ResponseEntity<?> main() { List<Board> boardList = boardService.글목록조회(); return ResponseEntity.ok(new ApiUtil(boardList)); } // TODO : 글 상세보기 API 필요 -> @GetMapping("/api/boards/{id}/detail") @GetMapping("/api/boards/{id}/detail") public ResponseEntity<?> detail(@PathVariable Integer id) { User sessionUser = (User) session.getAttribute("sessionUser"); Board board = boardService.글상세보기(id, sessionUser); return ResponseEntity.ok(new ApiUtil(board)); } // TODO : 글 조회 API 필요 -> @GetMapping("/api/boards/{id}") // 글상세보기 -> 글, 유저 정보, 댓글까지 다 있음 +)동사 추가함 detail // 글조회 -> 글만 조회 하는거라 글 수정할 때 필요 @GetMapping("/api/boards/{id}") public ResponseEntity<?> findOne(@PathVariable Integer id) { Board board = boardService.글조회(id); return ResponseEntity.ok(new ApiUtil(board)); }
 
 
Share article

codingb