좌충우돌 개발공부

TIL(20240621) [아웃소싱프로젝트: 팔로우 구현]


📌 Spring

💡 아웃소싱 프로젝트 : 팔로우 구현

이번 프로젝트의 주제를 익명게시판으로 했기 때문에 익명게시판/비익명게시판으로 구분하였고, 비익명게시판 조회에서 자신이 팔로우한 사람들의 게시물을 최신순으로 조회할 수 있도록 구현해보았다.

  • 팔로우 기능 구현
    • 특정 사용자를 팔로우 / 언팔로우를 할 수 있습니다.
    • 팔로우 기능이 구현되었다면 뉴스피드에 팔로우하는 사용자의 게시물을 볼 수 있습니다.
    • 팔로우를 하고 있는 사람들이 작성한 게시물을 볼 때 정렬 기준은 최신순입니다.

public List<PublicPostResponseDto> getFollowedPosts(User user, int page, int pageSize) {
    Pageable pageable = PageRequest.of(page, pageSize);

    User currentUser = userService.findByUserSeq(user.getUserSeq());
    List<Follow> follows = followRepository.findByFromUser(currentUser);

    List<PublicPostResponseDto> publicPostResponseDtos = new ArrayList<>();
    for (Follow followerList : follows) {
      User followedUser = followerList.getToUser();
      Page<PublicPost> publicPosts = postRepository.findByUserOrderByCreatedAtDesc(followedUser, pageable);
      if (publicPosts.getTotalElements() == 0) {
        throw new CustomException(ErrorCode.POST_EMPTY);
      }
      for(PublicPost publicPost : publicPosts) {
        PublicPostResponseDto responseDto = new PublicPostResponseDto(publicPost);
        publicPostResponseDtos.add(responseDto);
      }
    }
    return publicPostResponseDtos;
  }

구현한 코드에 대해서 간단하게 설명을 해보자면 userService에서 user의 정보를 가져오고 currentUser가 팔로우한 모든 사용자를 가져온 다음 그 모든 사용자의 publicPost를 가져오고 최신순으로 페이지 처리를 한다. publicPosts가 0 이라면 예외를 날리고 아니라면 reponseDto에 객체에 publicPost를 담아서 반환한다.

정말 간단한거 같지만 코드 구현하는데 엄청 머리가 터질뻔했다..ㅎㅎㅎ;;; 너무 복잡하고 일단 FromUser와 ToUser가 헷갈리기 시작하면서 Follwing과 Followers도 헷갈리고..

어쨋든 정리하자면 FromUser = “나” ToUser = “내가 팔로우한 사람” List = "내가 팔로우 한 사람들 리스트" List = "나를 팔로우 한 사람들 리스트"

if (publicPosts.getTotalElements() == 0) {
        throw new CustomException(ErrorCode.POST_EMPTY);
      }

그리고 위 부분에서 다른 포스트 전체조회에서는 되지만 나를 팔로우한 사람들의 게시물 전체조회에서는 예외처리가 되지않아 아래와 같이 수정하였다.

if (publicPosts.isEmpty()) {
        throw new CustomException(ErrorCode.POST_EMPTY);
      }

추가로 controller 쪽에서 page처리


@GetMapping("/followed")
    public ResponseEntity<List<PublicPostResponseDto>> getFollowedPosts(
      @AuthenticationPrincipal UserDetailsImpl userDetails, @RequestParam("page") int page, @RequestParam(value = "size", defaultValue = "5") int pageSize) {
     return ResponseEntity.status(HttpStatus.OK)
        .body(postService.getFollowedPosts(userDetails.getUser(), page - 1, pageSize));
     }

이전에는 pagesize를 상수로 지정해놓는 형식으로 하였으나 나중에 추후에 유지보수측면에서 좋지 않을 것으로 판단되어 pageSize를 param으로 받을 수 있도록 했다. 그리고 pageSize를 별도로 넘겨주지 않을 경우 defaultvalue를 통해서 5로 지정해놓았다. (5개만 보이도록 지정해놓았다)


@RestController
@RequiredArgsConstructor
@RequestMapping("/api")
public class FollowController {

  private final UserService userService;
  private final FollowService followService;

  // 팔로우
  @PostMapping("/follower")
  public ResponseEntity<String> followUser(@RequestBody @Valid FollowRequestDto requestDto, @AuthenticationPrincipal UserDetailsImpl userDetails) {
    User toUser = userService.findByUserSeq(requestDto.getToUserSeq());
    User fromUser = userService.findByUserSeq(userDetails.getUser().getUserSeq());
    followService.followUser(toUser, fromUser);
    return ResponseEntity.status(HttpStatus.OK).body(toUser.getUserId() + " 님을 팔로우 하였습니다.");
  }

  // 언팔로우
  @DeleteMapping("/follower")
 public ResponseEntity<String> deleteFollowUser(@RequestBody @Valid FollowRequestDto requestDto, @AuthenticationPrincipal UserDetailsImpl userDetails) {
    User toUser = userService.findByUserSeq(requestDto.getToUserSeq());
    User fromUser = userService.findByUserSeq(userDetails.getUser().getUserSeq());
    followService.deleteFollowUser(toUser, fromUser);
    return ResponseEntity.status(HttpStatus.OK).body(toUser.getUserId() + " 님을 팔로우취소 하였습니다.");
  }

  // 내가 팔로우한 사람들 조회
  @GetMapping("/followers")
  public ResponseEntity<List<FollowResponseDto>> getFollowings(@AuthenticationPrincipal UserDetailsImpl userDetails) {
    User user = userService.findByUserSeq(userDetails.getUser().getUserSeq());
    List<FollowResponseDto> followers = followService.getFollowings(user);
    return ResponseEntity.status(HttpStatus.OK).body(followers);
  }
}

코드 풀이 필요


@Service
@RequiredArgsConstructor
public class FollowService {

  private final FollowRepository followRepository;

  @Transactional
  public void followUser(User toUser, User fromUser) {
    if(fromUser.getUserSeq().equals(toUser.getUserSeq())) {
      throw new CustomException(ErrorCode.SAME_USER);
    }
    Optional<Follow> checkFollow = followRepository.findByFromUserAndToUser(fromUser, toUser);
    if(checkFollow.isPresent()) {
      throw new CustomException(ErrorCode.ALREADY_FOLLOW);
    }
    Follow follow = new Follow(fromUser, toUser);
    followRepository.save(follow);
  }

  @Transactional
  public void deleteFollowUser(User toUser, User fromUser) {
    Optional<Follow> checkFollow = followRepository.findByFromUserAndToUser(fromUser, toUser);
    if(checkFollow.isEmpty()) {
      throw new CustomException(ErrorCode.RECENT_NOT_FOLLOW);
    }
    followRepository.delete(checkFollow.get());
  }

  @Transactional(readOnly = true)
  public List<FollowResponseDto> getFollowings(User user) {
    List<FollowResponseDto> followResponseDtoList = new ArrayList<>();
    for(Follow follow : user.getFollowings()) {
      followResponseDtoList.add(new FollowResponseDto(follow.getToUser()));
    }
    return followResponseDtoList;
  }
}

팔로우한 사람들 조회 같은경우 user.getFollowings가 비어있는 경우 예외처리가 필요했으나 하지 않으니…… 빈 리스트가 떴고,

@Transactional(readOnly = true)
  public List<FollowResponseDto> getFollowings(User user) {
    List<FollowResponseDto> followResponseDtoList = new ArrayList<>();
    List<Follow> followings = user.getFollowings();
    if (followings.isEmpty()) {
      throw new CustomException(ErrorCode.EMPTY_FOLLOW);
    }
    for (Follow follow : followings) {
      followResponseDtoList.add(new FollowResponseDto(follow.getToUser()));
    }
    return followResponseDtoList;
  }

ErrorCode.EMPTY_FOLLOW 해당 에러코드를 만들어 exception처리했다. 정작 코드를 구현할 때는 생각도 못하다가 뒤늦게 테스트를 할 때 이러한 부분을 깨닫곤 한다. 아직 예외처리에 대한 경각심이 부족한게지…..😅


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

  @ManyToOne
  @JoinColumn(name = "from_user")
  private User fromUser;

  @ManyToOne
  @JoinColumn(name = "to_user")
  private User toUser;

  public Follow(User fromUser, User toUser){
    this.fromUser = fromUser;
    this.toUser =toUser;
  }
}


코드 풀이 필요