Springboot 팔로잉 및 팔로워 구현

조정우·2025년 4월 10일
0

오늘의 할일 : 팔로잉 및 팔로워 그리고 맞팔로워 , 취소

팔로워와 팔로잉 시스템 이해하기


팔로워와 팔로잉 시스템은 소셜 네트워크에서 사람들 간의 관계를 표현하는 중요한 개념입니다. 간단하게 말하면, 팔로잉은 내가 다른 사람을 팔로우하는 것이고, 팔로워는 다른 사람이 나를 팔로우하는 것입니다. .

1. 팔로잉 (Following)
내가 다른 사람을 팔로우하는 것을 말합니다.

팔로우하면, 그 사람의 업데이트나 게시물, 활동 등을 볼 수 있게 됩니다.

예를 들어, 1번 유저가 3번 유저를 팔로우하면, 1번 유저는 3번 유저의 게시물을 볼 수 있고, 3번 유저가 올린 콘텐츠에 대한 알림을 받을 수 있습니다.

2. 팔로워 (Follower)
다른 사람이 나를 팔로우하는 것을 말합니다.

내가 다른 사람을 팔로우한다고 해서 그 사람도 나를 팔로우하지 않는다면, 나는 그 사람의 팔로워가 됩니다.

예를 들어, 3번 유저가 1번 유저를 팔로우한다면, 3번 유저는 1번 유저의 팔로워가 됩니다. 이때 1번 유저는 3번 유저의 팔로워가 아니고, 3번 유저는 1번 유저를 팔로워하는 상태입니다.

3. 맞팔로우 (Mutual Following)
내가 다른 사람을 팔로우하고, 그 사람이 나를 팔로우할 때 맞팔로우가 됩니다.

즉, 양방향 팔로우가 이루어진 상태입니다. 이 상태에서 두 사람은 서로의 게시물을 볼 수 있고, 알림을 받을 수 있습니다.

예를 들어, 1번 유저가 3번 유저를 팔로우하고, 3번 유저도 1번 유저를 팔로우하면 두 사람은 맞팔로우 관계입니다. 이때, 두 사람은 서로 팔로우하고 있기 때문에, "팔로잉" 상태로 표시됩니다.

4. 팔로우 취소 (Unfollow)
팔로우를 취소하는 기능입니다. 내가 팔로우한 사람의 게시물이 더 이상 보고 싶지 않거나, 관계를 끊고 싶을 때 사용합니다.

예를 들어, 1번 유저가 3번 유저의 팔로우를 취소하면, 더 이상 3번 유저의 게시물을 볼 수 없게 됩니다

사실상 찜하기 기능과 비슷하다


그러면 스프링부트 코드에서 구현!!

1. follow DB 및 Entity 구현

  • follow_idx = 팔로우 번호
  • follow_loginidx = 현재 로그인한 유저(로그인한 유저가 누르는거니깐 팔로잉)
  • follow_useridx = 팔로잉을 한 유저의 번호
  • follow_at = 팔로잉한 시간
@Getter
@Setter
@Entity
@NoArgsConstructor
@AllArgsConstructor
public class Follow {
    @Id
    @GeneratedValue ( strategy = GenerationType.IDENTITY)
    private Long followIdx;

    @Column(nullable = false)
    private Long followLoginidx;

    @Column(nullable = false)
    private Long followUseridx;

    @Column(nullable = false)
    private LocalDateTime followAt;
}

2. follow Repository 구현

findByFollowLoginidxAndFollowUseridx = 현재 로그인한 유저가 팔로잉 했는지를 체크 현재 로그인 유저의 번호 AND 상대방의 유저의 번호

@Repository
public interface FollowRepository extends JpaRepository<Follow,Long> {
    Optional<Follow> findByFollowLoginidxAndFollowUseridx (Long loginidx , Long userid);
}

3. follow Service 구현

현재 팔로잉 했는지를 체크해주는 API이다
현재 로그인한유저의 IDX, 상대방의 userIDX 값을 데이터에서 비교해서 없으면 false 를 반납한다

//현재 팔로우 했는지를 체크
    @Transactional
    public boolean isFollowCheck(Long useridx, Long loginidx) {
        Optional<Follow> existingFollow = followRepository.findByFollowLoginidxAndFollowUseridx(loginidx, useridx);
        return existingFollow.isPresent();
    }

팔로잉 및 팔로잉 취소 토클
existingFollow 는 현재 팔로잉 상태를 체크를 한다 만약 팔로우가 DB가 비어있지 않았을 때 누르면 existingFollow.isPresent() 해당 Member 유저의 팔로우 팔로잉 을 숫자를 -1해주고 return false를 Controller에 보낸다
이후 followeRepository delete 삭제를한다

@Transactional
    public boolean addFollow(Long useridx, Long loginidx) {
        Optional<Follow> existingFollow = followRepository.findByFollowLoginidxAndFollowUseridx(loginidx, useridx);

        // 팔로잉 - 해당 유저의 팔로워 수가 증가
        Member member1 = memberRepository.findByIdx(loginidx)
                .orElseThrow(() -> new RuntimeException("게시글을 찾을 수 없습니다"));

        // 팔로워 - 해당 유저의 팔로잉 수가 증가 
        Member member2 = memberRepository.findByIdx(useridx)
                .orElseThrow(() -> new RuntimeException("게시글을 찾을 수 없습니다"));

        if (existingFollow.isPresent()) {
            member1.setFollowing(member1.getFollowing() - 1);
            memberRepository.save(member1);
            member2.setFollow(member2.getFollow() - 1);
            memberRepository.save(member2);

            followRepository.delete(existingFollow.get());
            return false;

        } else {
            member1.setFollowing(member1.getFollowing() + 1);
            memberRepository.save(member1);
            member2.setFollow(member2.getFollow() + 1);
            memberRepository.save(member2);

            Follow follow = new Follow(); // 객체 생성
            follow.setFollowUseridx(useridx);
            follow.setFollowLoginidx(loginidx);
            follow.setFollowAt(LocalDateTime.now());
            followRepository.save(follow);
            return true;

        }

4. FolloweController 구현

  1. 팔로우 팔로잉을 체크한다
    Service에서 받아온 true/false 값을 바탕으로 Controller 에서 Map<String, Object> 형식으로 JSON 응답을 만들어 프론트(html/js) 로 보내준다
    boolean isMutual = 이는 현재 상대가 나를 팔로우 해주고있는지 체크를 해준다(맞팔로우 텍스트를 보여주기위해서이다)
// 팔로우 팔로잉 체크
    @GetMapping("/profile/follow/{memberIdx}")
    public ResponseEntity<Map<String, Object>> checkFollow(@PathVariable Long memberIdx, Principal principal) {
        Map<String, Object> response = new HashMap<>();

        if (principal == null) {
            return ResponseEntity.status(HttpStatus.UNAUTHORIZED)
                    .body(Map.of("status", "error", "message", "로그인이 필요합니다"));
        }

        String username = principal.getName();
        Member member = memberRepository.findByUserId(username)
                .orElseThrow(() -> new RuntimeException("해당 유저를 찾을 수 없습니다"));

        boolean isFollow = followService.isFollowCheck(memberIdx, member.getIdx());
        boolean isMutual = followService.isFollowCheck(member.getIdx(), memberIdx); // 상대가 나를 팔로우

        Long followCount = memberRepository.findByIdx(memberIdx)
                .map(Member::getFollow)
                .orElse(0L);
        response.put("followCount",followCount);

        if (isFollow) {
            response.put("status", "following");
        } else if (!isFollow && isMutual) {
            response.put("status", "mutual");
        } else {
            response.put("status", "not_following");
        }

        return ResponseEntity.ok(response);
    }
  1. 팔로우 팔로잉 직접 누르는 토글

이것또한 같다 Service에서 받아온 true/false 값을 바탕으로 Controller 에서 Map<String, Object> 형식으로 JSON 응답을 만들어 프론트(html/js) 로 보내준다

@PostMapping("/profile/following/{memberIdx}")
    public ResponseEntity<Map<String, Object>> toggleFollowing(@PathVariable Long memberIdx, Principal principal) {
        Map<String, Object> response = new HashMap<>();

        if (principal == null) {
            return ResponseEntity.status(HttpStatus.UNAUTHORIZED)
                    .body(Map.of("status", "error", "message", "로그인이 필요합니다"));
        }

        String username = principal.getName();
        Member member = memberRepository.findByUserId(username)
                .orElseThrow(() -> new RuntimeException("해당 유저를 찾을 수 없습니다"));

        boolean isFollowing = followService.addFollow(memberIdx, member.getIdx());

        Long followCount = memberRepository.findByIdx(memberIdx)
                .map(Member::getFollow)
                .orElse(0L);
        response.put("followCount",followCount);

        if(isFollowing){
            response.put("status","added");
        } else {
            response.put("status","removed");
        }
        return ResponseEntity.ok(response);
    }

5. html 에서 보여주기

  • 당연 Ajax JSON 형태로 보내준다 일단 팔로우 했는 지 안했는지 DOMContentLoaded 페이지가 로드시에 Map으로 받아온 값을 확인한다 then(data 에서 받아온 status 값에 따라 보여주는 textContent 를 팔로잉 , 맞팔로우 하기 , 팔로우하기 로 보여준다

  • 아래에 follow-btn 을 누르면 data-member 값을
    memberIdx 에다가 저장 이후 그 값을 /profile/following/${memberIdx} 로 넘겨준다 POST 형식이후 data.status 값에 따라 팔로잉 , 팔로우 하기를 보여준다

<button class="follow-btn" th:data-member-idx="${member.idx}">
  팔로우 하기
</button>
 document.addEventListener("DOMContentLoaded", function () {
        const btn = document.querySelector(".follow-btn");
        const memberIdx = btn.dataset.memberIdx;
        const followingCountSpan = document.querySelector(".follower-count");


        fetch(`/profile/follow/${memberIdx}`, {
            method: "GET",
            headers: {
                "Content-Type": "application/json"
            }
        })
            .then(response => response.json())
            .then(data => {
                if (data.status === "following") {
                    btn.textContent = "팔로잉";
                    btn.classList.add("active");
                    followingCountSpan.textContent = data.followCount;
                } else if (data.status === "mutual") {
                    btn.textContent = "맞팔로우 하기";
                    btn.classList.remove("active");
                    followingCountSpan.textContent = data.followCount;
                } else if (data.status === "not_following") {
                    btn.textContent = "팔로우 하기";
                    btn.classList.remove("active");
                    followingCountSpan.textContent = data.followCount;
                }
            })
            .catch(error => console.error("Error:", error));

        btn.addEventListener("click", function () {
            const isFollowing = btn.classList.contains("active");

            // 언팔로우하려는 경우
            if (isFollowing) {
                const confirmUnfollow = confirm("팔로잉을 취소하시겠습니까?");
                if (!confirmUnfollow) {
                    return; // 사용자가 취소 누르면 아무 동작도 하지 않음
                }
            }

            fetch(`/profile/following/${memberIdx}`, {
                method: "POST",
                headers: {
                    "Content-Type": "application/json"
                }
            })
                .then(response => {
                    if (response.status === 401) {
                        alert("로그인을 해주시기 바랍니다");
                        window.location.href = "/login";
                        return;
                    }
                    return response.json();
                })
                .then(data => {
                    if (data.status === "added") {
                        btn.textContent = "팔로잉";
                        btn.classList.add("active");
                        followingCountSpan.textContent = Number(followingCountSpan.textContent) + 1;
                    } else if (data.status === "removed") {
                        btn.textContent = "팔로우 하기";
                        btn.classList.remove("active");
                        followingCountSpan.textContent = Number(followingCountSpan.textContent) - 1;
                    }
                })
                .catch(error => console.error("Error:", error));
        });
    });

6. 구현 이미지

1.qqqq 라는 유저가 로그인해서 1234라는 유저한테 팔로우를 신청하기함

2.1234라는 유저가 로그인해서 qqqq라는 유저 프로필로 들어가서 맞팔로우 하기

  1. DB에는 해당 유저들의 번호가 저장이 된다
profile
)개발( 마구잡이로 글쓰기

0개의 댓글