게시판 Lv5 - 회원탈퇴, 대댓글, 좋아요

박영준·2023년 7월 11일
0

Spring

목록 보기
41/58

Spring Security 가 적용된 프로젝트에 회원탈퇴, 대댓글, 좋아요 기능이 추가되었다.
이번엔 기능별로 코드를 정리해보고자 한다.

자세한 코드는 아래 링크에 있다.
Spring Lv5 과제의 깃허브 주소

1. 회원탈퇴

1) DB 에서 회원 데이터 삭제

UserController

    @DeleteMapping("/signOut")
    private ResponseEntity<MsgResponseDto> signOut(@RequestBody SignOutRequestDto signOutRequestDto, @AuthenticationPrincipal UserDetailsImpl userDetails) {
        userService.signOut(signOutRequestDto, userDetails.getUser());
        return ResponseEntity.ok(new MsgResponseDto("회원탈퇴 완료", HttpStatus.OK.value()));
    }
  • SignOutRequestDto 에 로그아웃을 위한 username 과 password 를 담아준다.
  • UserDetailsImpl 에는 인증된 회원의 정보가 담겨있다.

SignOutRequestDto

@Getter
@NoArgsConstructor
public class SignOutRequestDto {
    private String username;
    private String password;
}

UserService

public void signOut(SignOutRequestDto signOutRequestDto, User user) {

    // 사용자명 일치 여부 확인
    user = userRepository.findByUsername(signOutRequestDto.getUsername()).orElseThrow (
                () -> new CustomException(NOT_MATCH_INFORMATION)
    );

    // 비밀번호 일치 여부 확인
    if (!passwordEncoder.matches(signOutRequestDto.getPassword(), user.getPassword())) {
        throw new CustomException(NOT_MATCH_INFORMATION);
    }

    userRepository.deleteById(user.getId());
}
  • username 과 암호화된 password 의 일치 여부를 비교하고, DB 에서 해당 사용자의 정보를 삭제한다.

2) 탈퇴 시, 데이터 일괄 삭제

해당 회원이 게시글/댓글/대댓글 을 작성할 경우, 그 회원이 탈퇴하면 관련된 데이터들도 모두 삭제되도록 구현했다.
이를 위해서는 연관관계에서 설정이 필요하다.

User

@Entity
@Getter
@NoArgsConstructor
@Table(name = "users")
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(nullable = false, unique = true)
    private String username;

    @Column(nullable = false)
    private String password;

    @Column(nullable = false)
    @Enumerated(value = EnumType.STRING)
    private UserRoleEnum role;

    @OneToMany(mappedBy = "user", cascade = CascadeType.REMOVE)
    private List<Board> boardList = new ArrayList<>();

    @OneToMany(mappedBy = "user", cascade = CascadeType.REMOVE)
    private List<Comment> commentList = new ArrayList<>();

    @OneToMany(mappedBy = "user", cascade = CascadeType.REMOVE)
    private List<Reply> replyList = new ArrayList<>();

    public User(String username, String password, UserRoleEnum role) {
        this.username = username;
        this.password = password;
        this.role = role;
    }
}

게시글 목록(boardList), 댓글 목록(commentList), 대댓글 목록(replyList) 필드

  • @OneTOMany(mappedBy = "user", cascade = CascadeType.REMOVE) 이 걸려있다.
    • mappedBy 를 통해, 연관관계의 주인이 아니게 되고
    • cascade 의 REMOVE 를 이용한 영속성 전이를 통해, 연쇄적으로 삭제되도록 했다.

Board, Comment, Reply

	@ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "user_id", nullable = false)
    private User user;

Board, Comment, Reply 엔티티

  • @ManyToOne 와 @JoinColumn 로 연관관계의 주인으로 설정하면 된다.

2. 대댓글

ReplyController

기본적인 구성은 게시글, 댓글과 동일하다.
다만, 여기에 replyId 가 추가되었을 뿐이다.

	// 대댓글 작성
    @PostMapping("/{boardId}/{cmtId}")
    public ResponseEntity<ReplyResponseDto> createReply(@PathVariable Long boardId, @PathVariable Long cmtId, @RequestBody ReplyRequestDto replyRequestDto, @AuthenticationPrincipal UserDetailsImpl userDetails) {
        return ResponseEntity.ok(replyService.createReply(boardId, cmtId, replyRequestDto, userDetails.getUser()));
    }

    // 대댓글 수정
    @PutMapping("/{boardId}/{cmtId}/{replyId}")
    public ResponseEntity<ReplyResponseDto> updateReply(@PathVariable Long boardId, @PathVariable Long cmtId, @PathVariable Long replyId, @RequestBody ReplyRequestDto replyRequestDto, @AuthenticationPrincipal UserDetailsImpl userDetails) {
        return ResponseEntity.ok(replyService.updateReply(boardId, cmtId, replyId, replyRequestDto, userDetails.getUser()));
    }

    // 대댓글 삭제
    @DeleteMapping("/{boardId}/{cmtId}/{replyId}")
    public ResponseEntity<MsgResponseDto> deleteReply(@PathVariable Long boardId, @PathVariable Long cmtId, @PathVariable Long replyId, @AuthenticationPrincipal UserDetailsImpl userDetails) {
        return ResponseEntity.ok(replyService.deleteReply(boardId, cmtId, replyId, userDetails.getUser()));
    }

ReplyRequestDto

@Getter
@NoArgsConstructor
public class ReplyRequestDto {
    private String reply;
}
  • 대댓글만 요청하도록 구성했다.

ReplyResponseDto

@Getter
public class ReplyResponseDto {
    private Long id;
    private String username;
    private String reply;
    private LocalDateTime createdAt;
    private LocalDateTime modifiedAt;

    // 게시글과 함께 조회되는 대댓글, 대댓글 작성/수정
    public ReplyResponseDto(Reply reply) {
        this.id = reply.getId();
        this.username = reply.getUsername();
        this.reply = reply.getReply();
        this.createdAt = reply.getCreatedAt();
        this.modifiedAt = reply.getModifiedAt();
    }
}

Reply

@Entity
@Getter
@Table(name = "reply")
@NoArgsConstructor
public class Reply extends Timestamped {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

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

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

    @ManyToOne
    @JoinColumn(name = "board_id", nullable = false)
    private Board board;

    @ManyToOne
    @JoinColumn(name = "comment_id", nullable = false)
    private Comment comment;

    @ManyToOne
    @JoinColumn(name = "user_id", nullable = false)
    private User user;

    // 대댓글 작성
    public Reply(ReplyRequestDto replyRequestDto, Board board, Comment comment, User user) {
        this.reply = replyRequestDto.getReply();
        this.username = user.getUsername();
        this.comment = comment;
        this.board = board;
        this.user = user;
    }

    // 대댓글 수정
    public void update(ReplyRequestDto replyRequestDto) {
        this.reply = replyRequestDto.getReply();
    }
}

ReplyService

1) 대댓글 작성

public ReplyResponseDto createReply(Long boardId, Long cmtId, ReplyRequestDto replyRequestDto, User user) {
    // 게시글이 있는지
    Board board = boardRepository.findById(boardId).orElseThrow (
            () -> new CustomException(NOT_FOUND_BOARD)
    );

    // 댓글이 있는지
    Comment comment = commentRepository.findById(cmtId).orElseThrow(
            () -> new CustomException(NOT_FOUND_COMMENT)
    );

    Reply reply = new Reply(replyRequestDto, board, comment, user);
    Reply saveReply = replyRepository.save(reply);

    return new ReplyResponseDto(saveReply, checkReplyLike(reply.getId(), user));
}

2) 대댓글 수정

@Transactional
public ReplyResponseDto updateReply(Long boardId, Long cmtId, Long replyId, ReplyRequestDto replyRequestDto, User user) {
    // 게시글이 있는지
    Board board = boardRepository.findById(boardId).orElseThrow (
            () -> new CustomException(NOT_FOUND_BOARD)
    );

    // 댓글이 있는지
    Comment comment = commentRepository.findById(cmtId).orElseThrow (
            () -> new CustomException(NOT_FOUND_COMMENT)
    );

    // 대댓글이 있는지 & 사용자의 권한 확인
    Reply reply = userService.findByReplyIdAndUser(replyId, user);

    reply.update(replyRequestDto);

    return new ReplyResponseDto(reply, checkReplyLike(reply.getId(), user));
}

3) 대댓글 삭제

public MsgResponseDto deleteReply(Long boardId, Long cmtId, Long replyId, User user) {
    // 게시글이 있는지
    Board board = boardRepository.findById(boardId).orElseThrow (
            () -> new CustomException(NOT_FOUND_BOARD)
    );

    // 댓글이 있는지
    Comment comment = commentRepository.findById(cmtId).orElseThrow (
            () -> new CustomException(NOT_FOUND_COMMENT)
    );

    // 대댓글이 있는지 & 사용자의 권한 확인
    Reply reply = userService.findByReplyIdAndUser(replyId, user);

    replyRepository.delete(reply);

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

3. 좋아요

좋아요 기능은 유사하므로, 게시글/댓글/대댓글 좋아요 中 게시글 좋아요만 다룰 것이다.
또한 그 중에서도 게시글 전체 조회만 작성할 것이다.

BoardController

    @PostMapping("/board/like/{boardId}")
    public ResponseEntity<MsgResponseDto> saveBoardLike(@PathVariable Long boardId, @AuthenticationPrincipal UserDetailsImpl userDetails) {
        return ResponseEntity.ok(boardService.saveBoardLike(boardId, userDetails.getUser()));
    }
  • 해당 사용자가 좋아요를 눌렀는지에 대한 정보가 필요하므로, 인증된 사용자의 데이터를 매개변수로 활용한다.

BoardResponseDto

@Getter
@JsonInclude(JsonInclude.Include.NON_NULL)
public class BoardResponseDto {
    private Long id;
    private String title;
    private String username;
    private String contents;
    private int boardLikeCnt;
    private boolean boardLikeCheck;
    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, boolean boardLikeCheck) {
        this.id = board.getId();
        this.title = board.getTitle();
        this.username = board.getUsername();
        this.contents = board.getContents();
        this.boardLikeCnt = board.getBoardLikeList().size();
        this.boardLikeCheck = boardLikeCheck;
        this.createdAt = board.getCreatedAt();
        this.modifiedAt = board.getModifiedAt();
        this.commentList = commentList;
    }
}
  • 좋아요 개수는 int 로, 해당 회원의 좋아요 유/무는 boolean 으로 구별한다.

  • 좋아요 개수는 ArrayList 를 사용했으므로, size() 함수로 그 배열의 크기를 구할 수 있다.

BoardLike

@Entity
@Getter
@Table(name = "boardLike")
@NoArgsConstructor
public class BoardLike {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @ManyToOne
    @JoinColumn(name = "board_id", nullable = false)
    private Board board;

    @ManyToOne
    @JoinColumn(name = "user_id", nullable = false)
    private User user;

    public BoardLike(Board board, User user) {
        this.board = board;
        this.user = user;
    }
}
@Entity
@Getter
@Table(name = "board")
@NoArgsConstructor
public class Board extends Timestamped {

    ...

    @OneToMany(mappedBy = "board", cascade = CascadeType.REMOVE)
    private List<BoardLike> boardLikeList = new ArrayList<>();
    
    ...
    
}    

게시글 좋아요 : 게시글 = N : 1 다대일의 관계다.

BoardService

1) 게시글 좋아요 유/무 (false 면 좋아요 취소, true 면 좋아요)

    @Transactional
    public boolean checkBoardLike(Long boardId, User user) {
        return boardLikeRepository.existsByBoardIdAndUserId(boardId, user.getId());
    }

boolean 으로 유/무 를 구분해준다.

2) 게시글 좋아요 개수

    @Transactional
    public MsgResponseDto saveBoardLike(Long boardId, User user) {
        // 게시글이 있는지
        Board board = boardRepository.findById(boardId).orElseThrow (
                () -> new CustomException(NOT_FOUND_BOARD)
        );

        if (!checkBoardLike(boardId, user)) {
            BoardLike boardLike = new BoardLike(board, user);
            boardLikeRepository.save(boardLike);
            return new MsgResponseDto("게시글 좋아요", HttpStatus.OK.value());
        } else {
            boardLikeRepository.deleteByBoardIdAndUserId(boardId, user.getId());
            return new MsgResponseDto("게시글 좋아요 취소", HttpStatus.OK.value());
        }
    }

'1)'에서 선언한 메서드를 이용하여, 해당 회원이 좋아요를 눌렀는지를 판단한 후 좋아요 개수를 누적해준다.

3) 게시글 전체 조회

    @Transactional(readOnly = true)
    public List<BoardResponseDto> getBoardList(User user) {
        List<Board> boardList = boardRepository.findAllByOrderByCreatedAtDesc();

        // 게시글
        List<BoardResponseDto> boardResponseDtoList = new ArrayList<>();
        for (Board board : boardList) {
            // 댓글
            List<CommentResponseDto> commentList = new ArrayList<>();
            for (Comment comment : board.getCommentList()) {
                // 대댓글
                List<ReplyResponseDto> replyResponseDtoList = new ArrayList<>();
                for (Reply reply : comment.getReplyList()) {
                    replyResponseDtoList.add(new ReplyResponseDto(reply, replyService.checkReplyLike(reply.getId(), user)));
                }

                commentList.add(new CommentResponseDto(comment, replyResponseDtoList, commentService.checkCommentLike(comment.getId(), user)));
            }

            boardResponseDtoList.add(new BoardResponseDto(board, commentList, checkBoardLike(board.getId(), user)));
        }

        return boardResponseDtoList;
    }

ArrayList 를 이용하여 각각 게시글, 댓글, 대댓글을 담아줄 새로운 배열을 생성한 후
for문을 돌려 해당 배열에 데이터를 담아준다.

profile
개발자로 거듭나기!

0개의 댓글