게시판 Lv3 - 회원가입/로그인, 게시글, 댓글, jwt, 권한 부여

박영준·2023년 7월 5일
0

Spring

목록 보기
34/58

Spring Lv3 과제

1. 기능 요구 사항

  1. 회원 가입 API

    • username, password를 Client에서 전달받기
    • username은 최소 4자 이상, 10자 이하이며 알파벳 소문자(a~z), 숫자(0~9)로 구성되어야 한다.
    • password는 최소 8자 이상, 15자 이하이며 알파벳 대소문자(a~z, A~Z), 숫자(0~9), 특수문자로 구성되어야 한다.
    • DB에 중복된 username이 없다면 회원을 저장하고 Client 로 성공했다는 메시지, 상태코드 반환하기
    • 회원 권한 부여하기 (ADMIN, USER) - ADMIN 회원은 모든 게시글, 댓글 수정 / 삭제 가능
  2. 로그인 API

    • username, password를 Client에서 전달받기
    • DB에서 username을 사용하여 저장된 회원의 유무를 확인하고 있다면 password 비교하기
    • 로그인 성공 시, 로그인에 성공한 유저의 정보와 JWT를 활용하여 토큰을 발급하고,
      발급한 토큰을 Header에 추가하고 성공했다는 메시지, 상태코드 와 함께 Client에 반환하기
  3. 댓글 작성 API

    • 토큰을 검사하여, 유효한 토큰일 경우에만 댓글 작성 가능
    • 선택한 게시글의 DB 저장 유무를 확인하기
    • 선택한 게시글이 있다면 댓글을 등록하고 등록된 댓글 반환하기
  4. 댓글 수정 API

    • 토큰을 검사한 후, 유효한 토큰이면서 해당 사용자가 작성한 댓글만 수정 가능
    • 선택한 댓글의 DB 저장 유무를 확인하기
    • 선택한 댓글이 있다면 댓글 수정하고 수정된 댓글 반환하기
  5. 댓글 삭제 API

    • 토큰을 검사한 후, 유효한 토큰이면서 해당 사용자가 작성한 댓글만 삭제 가능
    • 선택한 댓글의 DB 저장 유무를 확인하기
    • 선택한 댓글이 있다면 댓글 삭제하고 Client 로 성공했다는 메시지, 상태코드 반환하기
  6. 예외 처리

    • 토큰이 필요한 API 요청에서 토큰을 전달하지 않았거나 정상 토큰이 아닐 때는 "토큰이 유효하지 않습니다." 라는 에러메시지와 statusCode: 400을 Client에 반환하기
    • 토큰이 있고, 유효한 토큰이지만 해당 사용자가 작성한 게시글/댓글이 아닌 경우에는 “작성자만 삭제/수정할 수 있습니다.”라는 에러메시지와 statusCode: 400을 Client에 반환하기
    • DB에 이미 존재하는 username으로 회원가입을 요청한 경우 "중복된 username 입니다." 라는 에러메시지와 statusCode: 400을 Client에 반환하기
    • 로그인 시, 전달된 username과 password 중 맞지 않는 정보가 있다면 "회원을 찾을 수 없습니다."라는 에러메시지와 statusCode: 400을 Client에 반환하기
  7. 전체 게시글 목록 조회 API

    • 제목, 작성자명(username), 작성 내용, 작성 날짜를 조회하기
    • 작성 날짜 기준 내림차순으로 정렬하기
    • 각각의 게시글에 등록된 모든 댓글을 게시글과 같이 Client에 반환하기
    • 댓글은 작성 날짜 기준 내림차순으로 정렬하기
  8. 게시글 작성 API

    • 토큰을 검사하여, 유효한 토큰일 경우에만 게시글 작성 가능
    • 제목, 작성자명(username), 작성 내용을 저장하고
    • 저장된 게시글을 Client 로 반환하기
  9. 선택한 게시글 조회 API

    • 선택한 게시글의 제목, 작성자명(username), 작성 날짜, 작성 내용을 조회하기
      (검색 기능이 아닙니다. 간단한 게시글 조회만 구현해주세요.)
    • 선택한 게시글에 등록된 모든 댓글을 선택한 게시글과 같이 Client에 반환하기
    • 댓글은 작성 날짜 기준 내림차순으로 정렬하기
  10. 선택한 게시글 수정 API

    • 수정을 요청할 때 수정할 데이터와 비밀번호를 같이 보내서 서버에서 비밀번호 일치 여부를 확인 한 후
    • 토큰을 검사한 후, 유효한 토큰이면서 해당 사용자가 작성한 게시글만 수정 가능
    • 제목, 작성 내용을 수정하고 수정된 게시글을 Client 로 반환하기
  11. 선택한 게시글 삭제 API

    • 삭제를 요청할 때 비밀번호를 같이 보내서 서버에서 비밀번호 일치 여부를 확인 한 후
    • 토큰을 검사한 후, 유효한 토큰이면서 해당 사용자가 작성한 게시글만 삭제 가능
    • 선택한 게시글을 삭제하고 Client 로 성공했다는 메시지, 상태코드 반환하기

2. API 명세서

3. ERD

기본적으로는 '다대일 단방향' 위주로 구성했다.

게시판 : 사용자 = N : 1 다대일 단방향

  • 게시판이 연관관계의 주인

댓글 : 사용자 = N : 1 다대일 단방향

  • 댓글이 연관관계의 주인

댓글 : 게시판 : N : 1 다대일 양방향

  • 댓글이 연관관계의 주인
  • 게시글 삭제 시, 댓글도 함께 삭제되도록 하기 위해서는 게시글 쪽에서 @OneToMany(cascade = CascadeType.REMOVE) 를 사용해줘야 한다.
  • 따라서, 댓글에서의 다대일 단방향과 함께 사용되므로, 결국 다대일 양방향 관계가 되는 것이다.

4. 프로젝트 환경 세팅

(1) 프로젝트 생성 및 Dependencies 설정

(2) application.properties

Lv2 와 동일하다

(3) build.gradle

Lv2 와 동일하다

  • Lv2 에서와 같이, JWT 세팅을 추가해줬다.

5. 구현

  • id 이름 수정

    • 게시판 id : 댓글 id 와의 구분을 위해 모두 boardId 로 수정

    • 댓글 id : cmtId

      그에 따라, @PathVariable 를 사용한 URL 도 수정

1) Controller

BoardController

(1)

게시글 작성, 전체/선택 조회, 수정은 Lv2 와 동일하다.

(2)

// 게시글 삭제
    @DeleteMapping("/board/{boardId}")
    public ResponseEntity<MsgResponseDto> deleteBoard(@PathVariable Long boardId, @RequestBody BoardRequestDto requestDto, HttpServletRequest request) {
        return ResponseEntity.ok(boardService.deleteBoard(boardId, requestDto, request));
    }

@RequestBody BoardRequestDto requestDto

  • 게시글 삭제 시, Token 일치여부와 함께 작성자명(usrname)으로 사용자 인증을 할 것이므로
    게시판 작성시 입력한 username 을 받아와서 게시글 삭제에 이용한다.

CommentController

기본적인 틀은 BoardController 과 동일하다

(1)

	// 댓글 작성
    @PostMapping("/{boardId}")
    public ResponseEntity<CommentResponseDto> createComment(@PathVariable Long boardId, @RequestBody CommentRequestDto commentRequestDto, HttpServletRequest request) {
        return ResponseEntity.ok(commentService.createComment(boardId, commentRequestDto, request));
    }

@PostMapping("/{boardId}")

  • 댓글을 작성하기 위해서는 어느 게시글에 댓글을 작성할 것인지를 정해야한다.
    • 따라서, 해당 게시글의 번호가 필요하다.

(2)

	// 댓글 수정
    @PutMapping("/{boardId}/{cmtId}")       // 해당 게시글 번호, 댓글 번호가 필요
    public ResponseEntity<CommentResponseDto> updateComment(@PathVariable Long boardId, @PathVariable Long cmtId, @RequestBody CommentRequestDto commentRequestDto, HttpServletRequest request) {
        return ResponseEntity.ok(commentService.updateComment(boardId, cmtId, commentRequestDto, request));
    }

	// 댓글 삭제
    @DeleteMapping("/{boardId}/{cmtId}")
    public ResponseEntity<MsgResponseDto> deleteComment(@PathVariable Long boardId, @PathVariable Long cmtId, @RequestBody CommentRequestDto commentRequestDto, HttpServletRequest request) {
        return ResponseEntity.ok(commentService.deleteComment(boardId, cmtId, commentRequestDto, request));
    }

@PutMapping("/{boardId}/{cmtId}")

  • 댓글이 작성된 게시글 id 와 댓글 id URL 가 필요하다.

ResponseEntity<MsgResponseDto>

  • 게시글 삭제에서처럼, 댓글 삭제 후 메시지를 반환할 것이므로 메서드 반환 타입을 ResponseEntity 로 감싼 MsgResponseDto 으로 헀다.

UserController

Lv2 와 동일하다.

2) Dto

requestDto

(1) SignupRequestDto

@Getter
@NoArgsConstructor
public class SignupRequestDto {
    @NotBlank
    private String username;
    @NotBlank
    private String password;
    private boolean admin = false;      // 디폴트 값은 false. 관리자 권한일 경우 true 로 변한다
    private String adminToken = "";
}
  • username 과 password 로 회원가입을 한다. (Lv2 와 동일)

  • 회원 가입 시, 관리자(admin) 일 경우

    • admin = true 와 adminToken 값(userService 에 선언해둔)이 필요하다.

(2) LoginRequestDto

Lv2 와 동일하다

(3) BoardRequestDto

@Getter
@NoArgsConstructor
public class BoardRequestDto {
    private String username;
    private String title;
    private String contents;
}

username

  • 게시글 작성에서는 무방하지만,
    수정 & 삭제 에서는 username 으로 인증이 필요하다.

(4) CommentRequestDto

@Getter
@NoArgsConstructor
public class CommentRequestDto {
    private String username;
    private String comment;
}

username

  • 댓글 작성에서는 무방하지만,
    수정 & 삭제 에서는 username 으로 인증이 필요하다.

responseDto

(1) BoardResponseDto

@Getter
@JsonInclude(JsonInclude.Include.NON_NULL)
public class BoardResponseDto {
    private Long id;
    private String title;
    private String username;
    private String contents;
    private LocalDateTime createdAt;
    private LocalDateTime modifiedAt;
    private List<CommentResponseDto> commentList = new ArrayList<>();       // 게시글 조회 시, 댓글 목록도 함께 조회

    // 게시글 작성
    public BoardResponseDto(Board board) {
        this.id = board.getId();
        this.title = board.getTitle();
        this.username = board.getUsername();
        this.contents = board.getContents();
        this.createdAt = board.getCreatedAt();
        this.modifiedAt = board.getModifiedAt();
    }

	게시글 전체/선택 조회, 수정 (+ 댓글 목록이 함께 조회) 
    public BoardResponseDto(Board board, List<CommentResponseDto> commentList) {
        this.id = board.getId();
        this.title = board.getTitle();
        this.username = board.getUsername();
        this.contents = board.getContents();
        this.createdAt = board.getCreatedAt();
        this.modifiedAt = board.getModifiedAt();
        this.commentList = commentList;
    }
}

private List<CommentResponseDto> commentList = new ArrayList<>()

  • 댓글 목록을 받아줄 필드가 필요하다.
  • ArrayList 의 유연성을 이용하여 댓글들의 추가가 계속해서 가능하도록 만든다.

public BoardResponseDto(Board board, List<CommentResponseDto> commentList)

  • 게시글 전체/선택 조회, 수정을 할때, 해당 게시글 아래에 속한 댓글 목록이 함께 조회되도록 board 와 commentList 를 매개변수로 받는다

(2) CommentResponseDto

@Getter
public class CommentResponseDto {
    private Long id;
    private String username;
    private String comment;
    private LocalDateTime createdAt;
    private LocalDateTime modifiedAt;

    // 게시글 전체/선택 조회, 수정 및 댓글 작성, 수정
    public CommentResponseDto(Comment comment) {
        this.id = comment.getId();
        this.username = comment.getUsername();
        this.comment = comment.getComment();
        this.createdAt = comment.getCreatedAt();
        this.modifiedAt = comment.getModifiedAt();
    }
}
  • 댓글을 작성일을 기준으로 내림차순 정렬이 필요하므로, 생성/수정 시간 필드가 필요하다

public CommentResponseDto(Comment comment)

  • 게시글 전체/선택 조회, 수정 및 댓글 작성, 수정 에 필요하다

(3) MsgResponseDto

Lv2 와 동일하다

3) Service

  • CustomException 을 사용하여 직접 정의한 에러 코드를 전역적으로 설정해주었다.

BoardService

  • 게시글 작성은 Lv2 와 동일하다
  • 작성, 수정, 삭제에 공통 적용되는 메서드에도 custom 예외 처리를 해준 부분을 제외하고는 Lv2 와 동일하다
    (JWT 토큰을 다루는 부분 등...)

(1)

	// 게시글 전체 조회
    @Transactional(readOnly = true)
    public List<BoardResponseDto> getBoardList() {
        List<Board> boardList = boardRepository.findAllByOrderByCreatedAtDesc();        // 1.

        List<BoardResponseDto> boardResponseDtoList = new ArrayList<>();        		// 2.

        for (Board board : boardList) {										// 3.
            List<CommentResponseDto> commentList = new ArrayList<>();
            for (Comment comment : board.getCommentList()) {        
                commentList.add(new CommentResponseDto(comment));
            }

            boardResponseDtoList.add(new BoardResponseDto(board, commentList)); 		// 4.
        }

        return boardResponseDtoList;        // 5.
    }
  • 게시글을 조회하면서, 댓글 목록도 해당 게시글과 함께 조회되도록 했다.

    1. DB 에 저장된 게시글을 생성시간 기준 내림차순으로 조회한 후, boardList 에 담는다.

    2. 게시글 + 댓글이 함께 조회될 새로운 boardResponseDtoList 배열 생성

    3. boardList 배열을 돌면서 (= 게시글 목록에 담긴 게시글들을 돌면서), 새롭게 생성한 댓글 목록 commentList 배열에 댓글들을 담는다

      • 단, 해당 댓글 목록들은 게시글에 속해 있는 것들이므로, board.getCommentList() 메서드로 호출해온다
    4. boardResponseDtoList 배열(게시글+댓글)에 게시글 board 과 댓글 목록 commentList 을 담는다

    5. boardResponseDtoList 배열을 반환

(2)

	// 게시글 선택 조회
    public BoardResponseDto getBoard(Long boardId) {
        Board board = boardRepository.findById(boardId).orElseThrow(
                () -> new CustomException(NOT_FOUND_BOARD)
        );

        List<CommentResponseDto> commentList = new ArrayList<>();
        for (Comment comment : board.getCommentList()) {
            commentList.add(new CommentResponseDto(comment));
        }

        return new BoardResponseDto(board, commentList);
    }
  • 게시글 아래에 댓글 목록 배열을 추가하면 된다.
    • 따라서, 게시글 전체 조회에 비해 간단하다

(3)

	// 게시글 수정
    @Transactional
    public BoardResponseDto updateBoard(Long boardId, BoardRequestDto requestDto, HttpServletRequest request) {
        User user = getUserFromToken(request);			// getUserFromToken 메서드를 호출

        Board board;

		// 사용자의 권한 확인
        if (user.getRole().equals(UserRoleEnum.ADMIN)) {
            board = boardRepository.findById(boardId).orElseThrow (
                    () -> new CustomException(NOT_FOUND_BOARD)
            );
        } else {
            board = boardRepository.findByIdAndUserId(boardId, user.getId()).orElseThrow (
                    () -> new CustomException(NOT_FOUND_BOARD)

            );
        }

		// username 일치 여부 확인
        if (requestDto.getUsername().equals(user.getUsername())) {
            board.update(requestDto);
        } else {
            throw new CustomException(AUTHORIZATION);
        }

        List<CommentResponseDto> commentList = new ArrayList<>();
        for (Comment comment : board.getCommentList()) {
            commentList.add(new CommentResponseDto(comment));
        }

        return new BoardResponseDto(board, commentList);
    }

사용자의 권한 확인

  • ADMIN 의 경우, 모든 게시글에 접근 권한이 있으므로, 해당 게시글만 찾는다면 바로 수정이 가능하다
  • USER 의 경우, 사용자 id 와 게시글 id 일치 여부를 통해 게시글 수정 가능 여부를 판단

username 일치 여부 확인

  • 게시글 작성 시 입력한 username 과 DB 에 저장된 username 일치 여부를 비교하여
    • 일치 할 경우, 바로 수정 가능하고
    • 불일치 할 경우, custom 예외를 던진다

commentList

  • 게시글 수정 후 해당 게시글이 response 로 반환 될 때, 속한 댓글도 함께 반환되도록 댓글을 추가해줬다.

(4)

	// 게시글 삭제
    public MsgResponseDto deleteBoard(Long boardId, BoardRequestDto requestDto, HttpServletRequest request) {
        User user = getUserFromToken(request);			// getUserFromToken 메서드를 호출

        Board board;

        // 토큰을 가진 사용자가 '관리자'라면
        if (user.getRole().equals(UserRoleEnum.ADMIN)) {
            board = boardRepository.findById(boardId).orElseThrow(
                    () -> new CustomException(NOT_FOUND_BOARD)
            );
        } else {
            board = boardRepository.findByIdAndUserId(boardId, user.getId()).orElseThrow (
                    () -> new CustomException(NOT_FOUND_BOARD)
            );
        }

        if (requestDto.getUsername().equals(user.getUsername())) {
            boardRepository.delete(board);
        } else {
            throw new CustomException(AUTHORIZATION);
        }

        return new MsgResponseDto("게시글을 삭제했습니다.", HttpStatus.OK.value());
    }

조건 충족 시 게시글을 삭제하고 삭제 완료 메시지를 반환하는 부분을 제외하고는 게시글 수정과 동일하다

CommentService

  • BoardService 에서와 같이, JWT 토큰으로 사용자 검증을 한다.

(1)

@Service
@RequiredArgsConstructor
public class CommentService {
    private final UserRepository userRepository;
    private final BoardRepository boardRepository;
    private final CommentRepository commentRepository;
    private final JwtUtil jwtUtil;
    
    ...
    
}    

기본적인 틀은 지금껏 사용한 service 단의 것들과 동일하다

(2)

	// 댓글 작성
    public CommentResponseDto createComment(Long boardId, CommentRequestDto commentRequestDto, HttpServletRequest request) {
        Board board = boardRepository.findById(boardId).orElseThrow (
                () -> new CustomException(NOT_FOUND_COMMENT)
        );

        User user = getUserFromToken(request);			// getUserFromToken 메서드를 호출

        Comment comment = new Comment(commentRequestDto, board, user);
        Comment saveComment = commentRepository.save(comment);

        return new CommentResponseDto(saveComment);
    }
  • 댓글을 작성하기 전, 먼저 해당 게시글이 있는지 확인해야 한다.

Comment comment = new Comment(commentRequestDto, board, user)

  • 입력한 댓글 내용과 DB 에 있다고 판단된 board, Token 을 보유한 사용자라고 판단된 user 정보를 담아 새로운 댓글을 만든다

(3)

	// 댓글 수정
    @Transactional
    public CommentResponseDto updateComment(Long boardId, Long cmtId, CommentRequestDto commentRequestDto, HttpServletRequest request) {
        // 해당 사용자가 토큰을 가진 사용자인지 확인
        User user = getUserFromToken(request);

        // 게시글이 있는지
        Board board = boardRepository.findById(boardId).orElseThrow (
                () -> new CustomException(NOT_FOUND_BOARD)
        );

        Comment comment;

        // 사용자의 권한 확인
        if (user.getRole().equals(UserRoleEnum.ADMIN)) {
            comment = commentRepository.findById(cmtId).orElseThrow (
                    () -> new CustomException(NOT_FOUND_COMMENT)
            );
        } else {
            comment = commentRepository.findByIdAndUserId(cmtId, user.getId()).orElseThrow (
                    () -> new CustomException(NOT_FOUND_COMMENT)
            );
        }

		// username 일치 여부 확인
        if (commentRequestDto.getUsername().equals(user.getUsername())) {
            comment.update(commentRequestDto);
        } else {
            throw new CustomException(AUTHORIZATION);
        }

        return new CommentResponseDto(comment);
    }
  • '사용자의 권한 확인' 과 'username 일치 여부 확인' 은 게시글 수정에서와 동일하다

(4)

	// 댓글 삭제
    public MsgResponseDto deleteComment(Long boardId, Long cmtId, CommentRequestDto commentRequestDto, HttpServletRequest request) {
        // 해당 사용자가 토큰을 가진 사용자인지 확인
        User user = getUserFromToken(request);

        // 게시글이 있는지
        Board board = boardRepository.findById(boardId).orElseThrow (
                () -> new CustomException(NOT_FOUND_BOARD)
        );

        Comment comment;

        // 토큰을 가진 사용자가 '관리자'라면
        if (user.getRole().equals(UserRoleEnum.ADMIN)) {
            comment = commentRepository.findById(cmtId).orElseThrow (      // 관리자는 모든 댓글을 삭제할 권한 있으므로, userId 와 일치여부까지 비교할 필요는 없다
                    () -> new CustomException(NOT_FOUND_COMMENT)
            );
        } else {
            comment = commentRepository.findByIdAndUserId(cmtId, user.getId()).orElseThrow (
                    () -> new CustomException(NOT_FOUND_COMMENT)
            );
        }

        if (commentRequestDto.getUsername().equals(user.getUsername())) {
            commentRepository.deleteById(cmtId);
        } else {
            throw new CustomException(AUTHORIZATION);
        }
        
        return new MsgResponseDto("댓글을 삭제했습니다.", HttpStatus.OK.value());
    }

댓글 삭제도 게시글 삭제와 기본적인 틀은 동일하다

UserService

	// 회원 가입
    public void signup(SignupRequestDto requestDto) {
        String username = requestDto.getUsername();
        String password = requestDto.getPassword();

        // 회원 중복 확인
        Optional<User> checkUsername = userRepository.findByUsername(username);
        if (checkUsername.isPresent()) {
            throw new CustomException(DUPLICATED_USERNAME);
        }

        // 사용자 ROLE 확인 (admin = true 일 경우 아래 코드 수행)
        UserRoleEnum role = UserRoleEnum.USER;
        if (requestDto.isAdmin()) {
            if (!ADMIN_TOKEN.equals(requestDto.getAdminToken())) {
                throw new CustomException(NOT_MATCH_ADMIN_TOKEN);
            }
            role = UserRoleEnum.ADMIN;
        }

        // 사용자 등록 (admin = false 일 경우 아래 코드 수행)
        User user = new User(username, password, role);
        userRepository.save(user);
    }
  • 사용자의 권한을 확인 (Admin / User) 하는 부분이 추가됐다.
    • Admin 일 경우, 제공한 ADMIN_TOKEN 값과 일치 여부를 판단하도록 했다.

로그인의 경우, custom 예외 처리를 제외하곤 Lv2 와 동일하다

4) Repository

BoardRepository

public interface BoardRepository extends JpaRepository<Board, Long> {
    List<Board> findAllByOrderByCreatedAtDesc();
    Optional<Board> findByIdAndUserId(Long boardId, Long userId);
}
  • 게시글 id 를 댓글 id 와 구분짓기 위해, boardId 로 수정했다.

CommentRepository

public interface CommentRepository extends JpaRepository<Comment, Long> {
    Optional<Comment> findByIdAndUserId(Long cmtId, Long userId);
}
  • 댓글 아이디를 cmtId 로 나타냈다.

  • Admin 이 아닌 일반 User 가 댓글 수정/삭제 시, id 일치 여부를 확인하기 위함이다.

UserRepository

Lv2 와 동일하다

5) Entity

Board

public class Board extends Timestamped {        
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(name = "title", nullable = false)
    private String title;

    @Column(name = "username", nullable = false)
    private String username;

    @Column(name = "contents", nullable = false, length = 500)
    private String contents;

    @ManyToOne(fetch = FetchType.LAZY)					// board : user = N : 1 다대일 단방향
    @JoinColumn(name = "user_id", nullable = false)
    private User user;

    @OneToMany(mappedBy = "board", cascade = CascadeType.REMOVE)        // comment : board = N : 1 다대일 양방향
    private List<Comment> commentList = new ArrayList<>();
    
    ...

}    

@OneToMany(mappedBy = "board", cascade = CascadeType.REMOVE)

  • cascade = CascadeType.REMOVE
    • @OneToMany 에서만 사용 가능
    • 영속성 전이 : REMOVE 를 이용해서, 게시글 삭제 시 댓글들도 같이 삭제되도록 했다.

private List<Comment> commentList = new ArrayList<>();

  • 게시글에는 댓글 목록을 담을 필드가 필요하다

나머진 Lv2 와 동일하다

Comment

(1)

public class Comment extends Timestamped {      
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(name = "username", nullable = false)
    private String username;

    @Column(name = "comment", nullable = false)
    private String comment;

    @ManyToOne								// comment : user = N : 1 다대일 단방향
    @JoinColumn(name = "user_id", nullable = false)
    private User user;

    @ManyToOne(fetch = FetchType.LAZY)		// comment : board = N : 1 다대일 양방향
    @JoinColumn(name = "board_id", nullable = false)
    private Board board;
    
    ...
    
}    

(2)

	// 댓글 작성
	public Comment(CommentRequestDto commentRequestDto, Board board, User user) {
        this.comment = commentRequestDto.getComment();
        this.username = user.getUsername();
        this.board = board;
        this.user = user;
    }

	// 댓글 수정
    public void update(CommentRequestDto commentRequestDto) {
        this.comment = commentRequestDto.getComment();
    }
  • 댓글 작성 시

    • 선택한 게시글에 댓글을 달아야 하므로, board 데이터가 필요하다
    • Token 을 보유한 사용자만 댓글을 달 수 있으므로, user 데이터가 필요하다.
  • 단. 댓글 수정 시에는

    • 댓글 요청request 시에 입려한 username 과 DB 에 저장된 username 과의 비교만 하면 되므로,
      commentRequestDto 에서의 comment 만 있으면 된다.

User

(1)

public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(nullable = false, unique = true)
    @Size(min = 4,max = 10, message ="작성자명은 4자 이상 10자 이하만 가능합니다.")
    @Pattern(regexp = "^[a-z0-9]*$", message = "작성자명은 알파벳 소문자, 숫자만 사용 가능합니다.")
    private String username;

    @Column(nullable = false)
    @Size(min = 8,max = 15, message ="비밀번호는 8자 이상 15자 이하만 가능합니다.")
    @Pattern(regexp = "^[a-zA-Z_0-9`~!@#$%^&*()+|={};:,.<>/?]*$", message = "비밀번호는 알파벳 대소문자, 숫자, 특수문자만 사용 가능합니다.")
    private String password;

    @Column(nullable = false)
    @Enumerated(value = EnumType.STRING)
    private UserRoleEnum role;
  • 비밀번호에는 특수문자 또한 사용가능하도록 정규표현식을 수정했다.

@Enumerated(value = EnumType.STRING)

  • Enum 타입을 저장할 때 사용

  • value = EnumType.STRING 을 사용해서 Enum 의 이름 그대로 DB 에 저장

  • Lv2 와 달리 권한(role)이 추가됐다.

    *만약, 해당 사용자가 작성한 게시글 & 댓글 목록을 조회하고 싶다면

    @OneToMany(mappedBy = "user")
    private List<Board> boardList = new ArrayList<>();
    
    @OneToMany(mappedBy = "user")
    private List<Comment> commentList = new ArrayList<>();

    양방향 연관관계를 지어줄 수도 있다.
    이렇게 될 수 있는 이유는 여기에 적힌 코드들이 연관관계의 주인이 아니기 때문이다.(mappedBy 가 사용됐음)
    따라서, 이들은 오로지 '조회'만 가능하게 된다.
    참고: 기본 키 PK vs 외래 키 FK

(2)

public User(String username, String password, UserRoleEnum role) {
        this.username = username;
        this.password = password;
        this.role = role;
    }
  • 동일하게 권한이 추가됐다.
    • 회원가입 시, 해당 회원의 권한을 Admin 으로 할 수 있게 됐다.

UserRoleEnum

public enum UserRoleEnum {
    USER,  // 사용자 권한
    ADMIN  // 관리자 권한
}
  • 회원의 권한 부여를 위한 클래스
    • enum 클래스

6) Timestamped

(1) Timestamped

Lv1 과 동일하다

(2) SpringProjectApplication

Lv1 과 동일하다

7) JwtUtil

Lv2 와 동일하다

8) exception

참고: @RestControllerAdvice 예외 처리 구현하기

예외 처리에 대한 부분은 따로 정리해두었다.

profile
개발자로 거듭나기!

0개의 댓글