Coding History

국비 지원 IT(웹앱개발) 취업반 강의 53일차 (Spring)

BlackBirdIT 2024. 8. 22. 08:44

조회수 기능을 만들고 보니까 없는 게시물에 대한 처리를 하지 않았다. 그에 대한 처리를 해주자.

    @RequestMapping("/usr/article/detail")
    public String showDetail(HttpServletRequest req, Model model, int id) {

        Rq rq = (Rq) req.getAttribute("rq");

        ResultData increaseHitCountRd = articleService.increaseHitCount(id);

        if (increaseHitCountRd.isFail()) return rq.historyBackOnView(increaseHitCountRd.getMsg());

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

        model.addAttribute("article", article);

        return "usr/article/detail";
    }

//////////
    //조회수
    public ResultData increaseHitCount(int id) {
        int affectRows = articleRepository.increaseHitCount(id);

        if (affectRows == 0)
            return ResultData.from("F-1", "해당 게시글은 존재하지 않습니다.", "id", id);

        return ResultData.from("S-1", "해당 게시글의 조회수가 증가합니다.", "id", id);
    }

이렇게 수정했다.


잘 동작하는 거 확인.

강사님이 조회수를 증가시키는 메서드를 따로 빼서 만드셨다. 원래는 그냥 이 상태로 써도 된다고 하셨는데, 비교적 간단한 조회수 기능을 사용해 Ajax를 적용시켜보려고 한다고 하셨다. 우리는 웹 페이지, 즉 JSP의 a태그나 from, input등을 사용해 url에 접근, 이후 자바 코드로 계속 정보를 교환했는데 이렇게하면 하나의 메서드밖에 사용하지 못한다. 하지만 메서드를 분리 시켰기때문에 지금 디테일 페이지에 접근할 때 메서드 두개를 실행시켜야되는 상황이다.

일단 말 한대로 메서드를 추가한다.

    @RequestMapping("/usr/article/doIncreaseHitCountRd")
    @ResponseBody
    public ResultData doIncreaseHitCount(int id) {

        ResultData increaseHitCountRd = articleService.increaseHitCount(id);

        if (increaseHitCountRd.isFail()) {
            return increaseHitCountRd;
        }

        return ResultData.newData(increaseHitCountRd, "hitCount", articleService.getArticleHitCount(id));
    }
    public Object getArticleHitCount(int id) {
        return articleRepository.getArticleHitCount(id);
    }
    @Select("""
            SELECT hitCount
            FROM article
            WHERE id = #{id}
                """)
    public int getArticleHitCount(int id);

이렇게,

detail페이지에 접근 할 때, 위의 메서드를 어떻게 불러오는가? 가 우리가 해결해야할 과제이다.

<script>
    const params = {};
    params.id = parseInt('${param.id}');
</script>

<script>
    function ArticleDetail__doIncreaseHitCount() {
        $.get('../article/doIncreaseHitCountRd', {
            id : params.id,
            ajaxMode : 'Y'
        }, function(data) {
            console.log(data);
            console.log(data.data1);
            $('.article-detail__hit-count').empty().html(data.data1);
        }, 'json')
    }
    $(function() {
                ArticleDetail__doIncreaseHitCount();
        //setTimeout(ArticleDetail__doIncreaseHitCount, 2000);
    })
</script>
///////////////////////////////////////////////////
    <div class="detail-item">
        <span class="label">조회수: <span class=" article-detail__hit-count">${article.hitCount}</span></span> 
    </div>

바로 이렇게 접근한다. ajax가 여기서 사용되는데 setTimeout으로 확인해보면 2초뒤에 조회수가 오르는 것도 확인 할 수 있다.

그래서 그냥 디테일페이지의 전체화면을 보자면


잘 작동하는 것을 확인할 수 있다. 아까 2초뒤에 조회수가 오른다고 했는데 여기서 비동기 호출을 확인할 수 있다. 그 영역만 새로고침되는 것이다. 이를 잘 활용하면 회원가입할 때 창 전체를 새로고침하지 않아도 비번과 비번확인 일치나, 아이디가 존재하는가에대한 표시를 실시간으로 할 수 있게 된다.

다음으로는 로컬 스토리지를 사용해서 좋아요 버튼을 detail에 추가했다.

<script>
    $(document).ready(function() {
        const likeBtn = $('#likeBtn');

        // 페이지 로드 시, 로컬 스토리지에서 좋아요 상태를 가져와 설정
        if (localStorage.getItem('liked_${article.id}') === 'true') {
            likeBtn.addClass('liked');
            likeBtn.text('❤️ Liked');
        }

        // 좋아요 버튼 클릭 이벤트
        likeBtn.on('click', function() {
            if (likeBtn.hasClass('liked')) {
                // 좋아요 취소
                likeBtn.removeClass('liked');
                likeBtn.text('👍 Like');
                localStorage.setItem('liked_${article.id}', 'false');
            } else {
                // 좋아요 설정
                likeBtn.addClass('liked');
                likeBtn.text('❤️ Liked');
                localStorage.setItem('liked_${article.id}', 'true');
            }
        });
    });
</script>
     <button id="likeBtn">👍 Like</button>

이렇게 한묶음으로 보면 되고, 화면상으로는 이렇게 보인다.


좋아요 누르기 전이고, 클릭을 하게 되면

이런 느낌으로 구현했다. 추후에 상세한 좋아요 개수 카운트, 그리고 인기글을 구현할 수 있는 초석이라고 하면 좋을 것 같다.

로컬 스토리지를 다시 활용해서 조회수 증가 js 코드를 수정한다. 뭘 수정하냐면 의도적으로 조회수를 늘리는 것을 방지하기 위해서 사용하는 것이다. 로그인을 하지 않은 사용자가 계속 접근을 시도해서 조회수를 늘린다면 방지할 방법이 없어서 클라이언트 단계에서 데이터를 저장하는 로컬 스토리지를 사용하는 것. 난 개인적으로 세션 스토리지가 더 맞는 것 같긴한데(조회수에 너무 각박하게 굴 필요는 없다는 생각이 들어서) 일단은 강사님이 하신대로 따라해보면,

우선 detail JSP의 script, 그러니까 우리가 조회수 기능을 만들었던 script로 접근을 하는 것 부터 시작이다.

<script>
    function ArticleDetail__doIncreaseHitCount() {
        //로컬스토리지, 조회수 조작 방지.
        const localStorageKey = 'article__' + params.id + '__alreadyOnView';
        if (localStorage.getItem(localStorageKey)) {
            return;
        }
        localStorage.setItem(localStorageKey, true);

        $.get('../article/doIncreaseHitCountRd', {
            id : params.id,
            ajaxMode : 'Y'
        }, function(data) {
            console.log(data);
            console.log(data.data1);
            $('.article-detail__hit-count').empty().html(data.data1);
        }, 'json')
    }
    $(function() {
                ArticleDetail__doIncreaseHitCount();
        //setTimeout(ArticleDetail__doIncreaseHitCount, 2000);
    })
</script>

이렇게 몇줄만 추가해주면 된다. 로컬 스토리지와 세션 스토리지는 따로 정리해서 올리겠다.

만약 세션 스토리지를 사용하고 싶다면 local을 session으로 바꾸기만 하면 아마 문제없이 작동할 것이다. 우선 작동되는 것을 확인해보자.


한번도 내가 접근한적이 없는 게시글이고, 따라서 if문을 패스하고 localStorage.setItem(localStorageKey, true); 를 부여받으면서 아래의 기존 조회수 메서드까지 통과된다. 그래서 조회수 1이 오른 모습.

원래는 새로고침을 할 때 마다 조회수가 카운트 되었지만 현제 로컬 스토리지를 추가하고 난 다음에는 application을 까보면

local storge가 보인다. 여길 까보면,


아까 만든 좋아요 기능과 함께 alreadyOnView 가 추가된 것이 보인다. 여기서 강제로 삭제하고 새로고침하면 다시 조회수는 오르고, 다시 저 값을 부여한다.

다시 좋아요 기능으로 넘어가서 Count를 추가했다.

<script>
    $(document).ready(
            function() {
                const likeBtn = $('#likeBtn');

                let likeCount = parseInt(localStorage
                        .getItem('likeCount_${article.id}')
                        || '0'); // 로컬 스토리지에서 가져온 초기값 설정
                const likeCountElement = $('#likeCount'); // likeCount 표시 요소

                // 초기 likeCount 설정
                likeCountElement.text(likeCount);

                // 페이지 로드 시, 로컬 스토리지에서 좋아요 상태를 가져와 설정
                if (localStorage.getItem('liked_${article.id}') === 'true') {
                    likeBtn.addClass('liked');
                    likeBtn.text('❤️');
                }
                // 좋아요 버튼 클릭 이벤트
                likeBtn.on('click', function() {
                    if (likeBtn.hasClass('liked')) {
                        // 좋아요 취소
                        likeBtn.removeClass('liked');
                        likeBtn.text('👍');
                        localStorage.setItem('liked_${article.id}', 'false');
                        likeCount--;
                    } else {
                        // 좋아요 설정
                        likeBtn.addClass('liked');
                        likeBtn.text('❤️');
                        localStorage.setItem('liked_${article.id}', 'true');
                        likeCount++;
                    }
                    // likeCount 업데이트 및 저장
                    likeCountElement.text(likeCount);
                    localStorage.setItem('likeCount_${article.id}', likeCount);
                });
            });
</script>

    <button id="likeBtn">👍</button>
    <span id="likeCount" class="like-count">0</span>

Count를 위해서는 변수가 두개 필요했다. 표시할 수 있는 요소 하나와 세는 요소 하나.

그래서 기능을 보면,


좋아요를 누른 상대고 count가 1이 오른 모습이다.

local stroge에 잘 저장 된 모습이다.

좋아요를 취소하면 -1과 false가 적용 되는 것도 확인 할 수 있다.

3624는 전에 테스트 했던 게시글이다. 신경쓰지 않아도 된다.

# reactionPoint 테이블 생성
CREATE TABLE reactionPoint(
    id INT(10) UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
    regDate DATETIME NOT NULL,
    updateDate DATETIME NOT NULL,
    memberId INT(10) UNSIGNED NOT NULL,
    relTypeCode CHAR(50) NOT NULL COMMENT '관련 데이터 타입 코드',
    relId INT(10) NOT NULL COMMENT '관련 데이터 번호',
    `point` INT(10) NOT NULL
);

# reactionPoint 테스트 데이터 생성
# 1번 회원이 1번 글에 싫어요
INSERT INTO reactionPoint
SET regDate = NOW(),
updateDate = NOW(),
memberId = 1,
relTypeCode = 'article',
relId = 1,
`point` = -1;

# 1번 회원이 2번 글에 좋아요
INSERT INTO reactionPoint
SET regDate = NOW(),
updateDate = NOW(),
memberId = 1,
relTypeCode = 'article',
relId = 2,
`point` = 1;

# 2번 회원이 1번 글에 싫어요
INSERT INTO reactionPoint
SET regDate = NOW(),
updateDate = NOW(),
memberId = 2,
relTypeCode = 'article',
relId = 1,
`point` = -1;

# 2번 회원이 2번 글에 싫어요
INSERT INTO reactionPoint
SET regDate = NOW(),
updateDate = NOW(),
memberId = 2,
relTypeCode = 'article',
relId = 2,
`point` = -1;

# 3번 회원이 1번 글에 좋아요
INSERT INTO reactionPoint
SET regDate = NOW(),
updateDate = NOW(),
memberId = 3,
relTypeCode = 'article',
relId = 1,
`point` = 1;

좋아요 기능을 위해서 테이블 생성을 하고 수업을 끝냈다.