Coding History

UPDATE JOIN 적용 AJAX 실시간 좋아요 반영

BlackBirdIT 2024. 8. 26. 09:16

기존에 만들어 둔 UPDATE JOIN 쿼리는 있으니 그대로 가져와서 메서드 실행할 때 두개의 쿼리가 작동하게끔 짰다.

짜보니까 반영이 잘 되는 것 같고 확인하기 위해서 alert를 띄우던 코드를 지웠다.

그런데 이게 문제가 좋아요도 잘 되고, 싫어요, 좋아요가 되어있을 때 싫어요를 클릭하면, 좋아요 취소와 동시에 싫어요가 반영되는 건 잘 된다.

근데 좋아요가 되어있을 때 좋아요 취소와 싫어요가 되어있을 때 싫어요 취소가 안된다. 다른건 잘 작동하니까 아마 쿼리가 잘못된거라고 예상한다. 일단은 지금까지 짜 놓은 코드를 한번 보자.

    @RequestMapping("/article/doReaction")
    @ResponseBody
    public ResultData doReaction(HttpServletRequest req , Model model, int id, String relTypeCode, int relId, int newPoint) {
        Rq rq = (Rq) req.getAttribute("rq");
        int loginedMemberId = rq.getLoginedMemberId();
        model.addAttribute("loginedMemberId", loginedMemberId);
        relTypeCode = "article";

        int resultPoint = reactionPointService.toggleReactionPoint(rq.getLoginedMemberId(), relTypeCode, relId, newPoint);

        System.err.println(resultPoint);

        String status = resultPoint > 0 ? "liked" : "unliked";
        ResultData rd = ResultData.from("S-1", "리액션기능 실행완료.", "status", status);
        rd.setData2("reactedArticleId", id);

        reactionPointService.updateArticleReactionPoints();

        return rd;
    }
@Service
public class ReactionPointService {
    @Autowired
    private ReactionPointRepository reactionPointRepository;

    public int toggleReactionPoint(int loginedMemberId, String relTypeCode, int relId, int newPoint) {

        ReactionPoint existingReaction = reactionPointRepository.getReactionPointByMemberIdAndRelId(loginedMemberId, relTypeCode, relId);

        System.err.println(existingReaction);

        if (existingReaction == null) {
            // 현재 반응이 없을 경우 -> 새로운 반응을 추가
            reactionPointRepository.insertReactionPoint(loginedMemberId, relTypeCode, relId, newPoint);
            return newPoint;
        } 

        if (existingReaction.getPoint() == 1 && newPoint == 0) {
            // 현재 좋아요인 경우 -> 좋아요를 취소
            reactionPointRepository.deleteReactionPoint(loginedMemberId, relTypeCode, relId);
            return 0;
        } 

        if (existingReaction.getPoint() == -1 && newPoint == 0) {
            // 현재 싫어요인 경우 -> 싫어요를 취소
            reactionPointRepository.deleteReactionPoint(loginedMemberId, relTypeCode, relId);
            return 0;
        }

        if (existingReaction.getPoint() == 1 && newPoint == -1) {
            // 현재 좋아요인 경우 -> 싫어요로 변경
            reactionPointRepository.updateReactionPoint(loginedMemberId, relTypeCode, relId, -1);
            return -1;
        }

        if (existingReaction.getPoint() == -1 && newPoint == 1) {
            // 현재 싫어요인 경우 -> 좋아요로 변경
            reactionPointRepository.updateReactionPoint(loginedMemberId, relTypeCode, relId, 1);
            return 1;
        }

        // 동일한 반응을 눌렀을 경우 취소
        reactionPointRepository.deleteReactionPoint(loginedMemberId, relTypeCode, relId);
        return 0;
    }

    public int getTotalReactionPoints(String relTypeCode, int relId) {
        Integer totalPoints = reactionPointRepository.getTotalReactionPoints(relTypeCode, relId);
        return totalPoints != null ? totalPoints : 0;
    }

    public void updateArticleReactionPoints() {
        reactionPointRepository.updateArticleReactionPoints();
    }
}
@Mapper
public interface ReactionPointRepository {

    @Select("SELECT * FROM reactionPoint WHERE memberId = #{memberId} AND relTypeCode = #{relTypeCode} AND relId = #{relId}")
    ReactionPoint getReactionPointByMemberIdAndRelId(int memberId, String relTypeCode, int relId);

    @Insert("INSERT INTO reactionPoint (regDate, updateDate, memberId, relTypeCode, relId, point) VALUES (NOW(), NOW(), #{memberId}, #{relTypeCode}, #{relId}, #{point})")
    void insertReactionPoint(int memberId, String relTypeCode, int relId, int point);

    @Delete("DELETE FROM reactionPoint WHERE memberId = #{memberId} AND relTypeCode = #{relTypeCode} AND relId = #{relId}")
    void deleteReactionPoint(int memberId, String relTypeCode, int relId);

    @Update("UPDATE reactionPoint SET point = #{point}, updateDate = NOW() WHERE memberId = #{memberId} AND relTypeCode = #{relTypeCode} AND relId = #{relId}")
    void updateReactionPoint(int memberId, String relTypeCode, int relId, int point);

    @Select("SELECT SUM(point) FROM reactionPoint WHERE relTypeCode = #{relTypeCode} AND relId = #{relId}")
    Integer getTotalReactionPoints(String relTypeCode, int relId);

    @Update("""
            UPDATE article AS A
            INNER JOIN (
                SELECT RP.relTypeCode, RP.relId,
                SUM(IF(RP.point > 0, RP.point, 0)) AS goodReactionPoint,
                SUM(IF(RP.point < 0, RP.point * -1, 0)) AS badReactionPoint
                FROM reactionPoint AS RP
                GROUP BY RP.relTypeCode, RP.relId
            ) AS RP_SUM
            ON A.id = RP_SUM.relId
            SET A.goodReactionPoint = RP_SUM.goodReactionPoint,
            A.badReactionPoint = RP_SUM.badReactionPoint;
        """)
    void updateArticleReactionPoints();
}

지금까지는 이렇고 위에서 말한 기능이 문제인데 이부분이 DELETE로 되어있는 부분이다.

라고 말하고 다시 해보니까 잘 되네;; JS에서 오류가 떠서 수정했는데 잘 된다.

$(document).ready(function() {

    const articleId = ${article.id};
    const relTypeCode = 'article';
    const relId = articleId;

    function reaction(point) {
        $.post('/article/doReaction', {
            id: articleId,
            relTypeCode: relTypeCode,
            relId: relId,
            newPoint: point
        }, function(response) {
            // 여기서 response를 처리할 수 있습니다.
            location.reload(); // 새로고침하여 결과 반영
        });
    }

    // 좋아요 버튼 클릭 시
    $('#likeBtn').on('click', function() {
        reaction(1); // 좋아요는 1로 설정
    });

    // 싫어요 버튼 클릭 시
    $('#disLikeBtn').on('click', function() {
        reaction(-1); // 싫어요는 -1로 설정
    });

});

오류

오류는 기존 코드에서 alert를 빼던 과정에서 생긴 것 같다.

치명적인 실수까지는 아니지만 치명적인 실수라고 한다면,

중첩된 function 선언:

$.post의 콜백 함수 내에 또 다른 function이 잘못된 위치에 선언되어 있습니다. 
JavaScript에서 중첩된 함수 선언이 이렇게 잘못되면 문법 오류가 발생합니다.

GPT가 찾아낸 오류가 이거였다.

function이 같은 위치에 두개 적혀 있었다. 레슨전에 수정한다고 급하게 하다가 못봤었나보다.

아무튼 기능적으로는 오류 없이 잘 돌아간다. 좋아요, 좋아요 취소, 싫어요, 싫어요 취소, 좋아요에서 싫어요, 싫어요에서 좋아요, 모든 경우의 수에서 문제 없이 작동하는 것을 볼 수 있다.

이제 CSS에서 디테일을 가다듬고.

하다보니까 CSS효과를 제대로 주려면 JS까지 고쳐야되네.

$(document).ready(function() {

    const articleId = ${article.id};
    const relTypeCode = 'article';
    const relId = articleId;

    function reaction(point) {
        $.post('/article/doReaction', {
            id: articleId,
            relTypeCode: relTypeCode,
            relId: relId,
            newPoint: point
        }, function(response) {
            if (response.resultCode.startsWith("S-")) {
                // 좋아요 또는 싫어요에 따라 버튼 스타일 업데이트
                if (point > 0) {
                    $('#likeBtn').addClass('liked').removeClass('unliked');
                    $('#disLikeBtn').removeClass('disliked');
                } else if (point < 0) {
                    $('#disLikeBtn').addClass('disliked').removeClass('unliked');
                    $('#likeBtn').removeClass('liked');
                }
            }
        });
    }

    // 좋아요 버튼 클릭 시
    $('#likeBtn').on('click', function() {
        reaction(1); // 좋아요는 1로 설정
    });

    // 싫어요 버튼 클릭 시
    $('#disLikeBtn').on('click', function() {
        reaction(-1); // 싫어요는 -1로 설정
    });

});
/* 좋아요 버튼 스타일 */
#likeBtn {
    padding: 10px 20px;
    font-size: 16px;
    background-color: transparent; /* 초기 상태는 투명 */
    color: #007bff; /* 파란색 텍스트 */
    border: 2px solid #007bff; /* 파란색 테두리 */
    border-radius: 5px;
    cursor: pointer;
    margin-right: 10px;
    transition: background-color 0.3s ease, color 0.3s ease;
}

#likeBtn.liked {
    background-color: #007bff; /* 파란색 배경 */
    color: #ffffff; /* 흰색 텍스트 */
}

/* 싫어요 버튼 스타일 */
#disLikeBtn {
    padding: 10px 20px;
    font-size: 16px;
    background-color: transparent; /* 초기 상태는 투명 */
    color: #6c757d; /* 회색 텍스트 */
    border: 2px solid red; 
    border-radius: 5px;
    cursor: pointer;
    margin-right: 10px;
    transition: background-color 0.3s ease, color 0.3s ease;
}

#disLikeBtn.disliked {
    background-color: red; /* 회색 배경 */
    color: #ffffff; /* 흰색 텍스트 */
}

/* 호버 효과 */
#likeBtn:hover {
    background-color: #0056b3; /* 어두운 파란색 배경 */
    color: #ffffff; /* 흰색 텍스트 */
    border: 2px solid #0056b3; /* 어두운 파란색 테두리 */
}

#likeBtn.liked:hover {
    background-color: #004085; /* 더 어두운 파란색 배경 */
    color: #ffffff;
}

#disLikeBtn:hover {
    background-color: #5a6268; /* 어두운 회색 배경 */
    color: #ffffff;
    border: 2px solid #5a6268; /* 어두운 회색 테두리 */
}

#disLikeBtn.disliked:hover {
    background-color: #343a40; /* 더 어두운 회색 배경 */
    color: #ffffff;
}

/* 좋아요 숫자 스타일 */
#likeCount {
    font-size: 18px;
    font-weight: bold;
    color: #007bff;
    margin-left: 10px;
}

/* 게시물 상세 정보 스타일 */
.detail-item {
    margin-bottom: 10px;
}

.label {
    font-weight: bold;
}

.actions {
    margin-top: 20px;
}

.navigation {
    margin-top: 30px;
}

해서 이제 마무리로 결과물은 이렇다.

지금 좋아요가 눌러져 있는 모습이고,

hover했을 때,

약간 이런느낌으로 만들었다. 근데 바꾸고 나니까 다시 좋아요 취소, 싫어요 취소가 작동을 잘 안해서 JS를 다시 고쳤다.

아 그리고 하는 김에 새로고침할 때 마다 버튼이 초기화 되는 것도 발견해서 그 부분도 고쳤다.

JS에는 해당 코드를 추가 햇고,

     // 서버에서 전달받은 사용자 반응 상태
    const userReactionPoint = ${userReactionPoint}; // 1: 좋아요, -1: 싫어요, 0: 반응 없음

    // 페이지 로드 시 버튼 초기화
    if (userReactionPoint === 1) {
        $('#likeBtn').addClass('liked');
    } else if (userReactionPoint === -1) {
        $('#disLikeBtn').addClass('disliked');
    }
    @RequestMapping("/usr/article/detail")
    public String showDetail( Model model, int id, HttpServletRequest req) {
        Rq rq = (Rq) req.getAttribute("rq");

        Article article = articleService.getForPrintArticle(rq.getLoginedMemberId(), id);

        model.addAttribute("article", article);

        int likeCount = reactionPointService.getTotalReactionPoints("article", id);
        model.addAttribute("likeCount", likeCount);

        // 사용자의 반응 상태 가져오기
        int userReactionPoint = reactionPointService.getUserReactionPoint(rq.getLoginedMemberId(), "article", id);
        model.addAttribute("userReactionPoint", userReactionPoint);

        return "usr/article/detail";
    }

///////

    public int getUserReactionPoint(int memberId, String relTypeCode, int relId) {
        ReactionPoint reactionPoint = reactionPointRepository.getReactionPointByMemberIdAndRelId(memberId, relTypeCode, relId);
        if (reactionPoint != null) {
            return reactionPoint.getPoint(); // 1: 좋아요, -1: 싫어요, 0: 반응 없음
        }
        return 0;
    }

컨트롤러와 서비스에서는 해당 코드 추가해서 사용자 상태를 저장하고 페이지에 로드 될 때 그 상태를 가져올 수 있게 만들었다.