토이 프로젝트/익명 채팅 사이트

프로젝트 진행 ⑦ : 메인페이지 구성, 글 목록 조회, 글 상세 조회 구성

OhPro 2022. 7. 25. 23:24
반응형

목차

  • 시작하며
  • 메인페이지
  • 글 목록 조회
  • 글 상세 조회
  • 마치며

 


 

시작하며

저번 포스팅에서는 회원가입 기능 및 PUT, DELETE 메서드 사용 가능하게 됨으로 인한 RESTful 한 URL 매핑 방식에 대해서 이야기했었는데, 로그인, 회원가입 기능이 정상적으로 작동하므로 본격적인 메인 콘텐츠인 글 작성, 조회를 구현한 방법이나 해결했던 문제 상황에 대해 포스팅하려고 한다.

 

물론 글 작성과 조회 말고도 수정과 삭제, 그 밖에도 구현해야 하는 기능들이 많지만 중간 포스팅을 하지 않으면 나중에 양이 엄청 많아질 것 같기에..

 

tmi 이긴 한데 요즘 본가에 데스크톱과 맥북을 오가며 깃허브에 대한 감각을 늘리고자 push, pull을 하고 있는데 뭔가 감이 잡히는 느낌이 들어서 좋다^^

 


 

메인 페이지

아래 사진은 설계 과정 때 만든 와이어 프레임이다.

 

메인페이지 와이어프레임

 

그리고 아래는 직접 작성한 페이지 UI(User Interface)이다.

 

메인페이지 UI (1)

 

메인페이지 UI (2)

 

UI 상단에 실시간 글, 일일 인기 글, 주간 인기 글은 라디오 버튼 형식의 입력 폼을 부트스트랩에서 가져온 후 css를 조정해서 만들었다. 각각의 버튼을 클릭하면 글 목록에서 정렬 방식에 변화를 줄 수 있도록 만들었다.

 

페이지네이션 + 푸터

 

푸터를 추가하기 전까진 몰랐었는데 이렇게 추가하니까 훨씬 괜찮아 보인다.. UI 하단에는 페이지네이션과 푸터를 위치시켰다.

 

메인 페이지는 글 목록을 디스플레이해야 하기 때문에 필연적으로 글 개수당 HTML 요소가 필요하다. 정확히는 부트스트랩 컴포넌트 중 하나인 카드를 사용했다.

 

그런데 나는 반복되고 조금씩 달라지는 데이터들을 그대로 두는 게 조금 심적으로 불편해서 이를 간략화하기 위해 JSTL을 아래와 같이 나열하는 용도로 사용했다.

 

메인페이지 jstl

 

위 소스의 요점은 대략 다음과 같다.

 

  • 부트스트랩 카드 컴포넌트 형식을 나열해보니 4 개씩 3열로 하면 적당해 보였다.
  • <table> 태그 태부에 <c:foreach...> 태그를 사용하여 <tr>의 반복을 간략화했다.
  • <tr> 태그 내부에 <c:foreach...>를 사용하여 <td>의 반복을 간략화했다.
  • <c:foreach...> 태그 내부에 <c:set...> 태그를 사용해서 행과 열(tr, td)을 list의 인덱스 변수로 만들었다.
  • <c:if...> 태그를 사용하여 만약 리스트가 끝났다면 더 이상 출력하지 않도록 조건문을 사용했다.

 

메인 페이지 요청 시 컨트롤러

 

위 사진은 로그인 성공 시 수행되는 리다이렉션에 의해 자동으로 수행되는 메서드이다. 메인 페이지의 콘텐츠는 글 목록을 보여주는 것이 전부지만 로그인에 성공해서 메인 페이지를 보여주는 상황에서 글 목록 요청 URL로 리다이렉션 하기엔 뭔가 의미가 맞지 않는 것 같아 따로 이렇게 만들었다.

 

소스 요점은 서비스 객체에서 페이지네이션의 기본 옵션을 불러와 세션에 저장하기와 request객체에 뭔가 Checked라는 이름의 파라미터를 만들어 저장하는 정도이다.

 

먼저 페이지네이션의 기본 옵션은 실시간 정렬 방식 + 첫 페이지의 옵션을 가지는데 여기서 실시간 정렬 방식은 위에 UI 사진에서 실시간 글을 클릭했을 때

 

디폴트 페이지네이션 옵션을 얻어오는 메서드

 

위 사진은 PostsOptionVO 형태를 반환하는 메서드로 현재 페이지네이션 관련 옵션이 4 개쯤 되는데 이 옵션들을 따로따로 세션에 저장하면 관리하기 어렵기 때문에 하나의 객체로 만든 것이 PostsOptionVO 클래스이다.

 

앞서 이야기했듯 생성자에서 첫 페이지와 실시간 정렬 방식("1"이 실시간 정렬 방식을 의미)을 입력받아 저장하고, setStartEndPageNo() 메서드를 사용해서 첫 페이지부터 글이 작성된 수만큼 페이지네이션 갯수를 조절하는 옵션을 설정해준다.

 

해당 메서드의 인자로 주입된 pageTotalCount와 5는 각각 실시간 정렬 방식 조회 시 검출된 글의 양을 토대로 전체 페이지 수페이지네이션 디스플레이 시 페이지를 몇 개까지 디스플레이할지 정해주는 파라미터들이다. 

 

예를 들어 5가 아닌 6이었다면, 하단에 "이전 1 2 3 4 5 6 다음"의 형식으로 디스플레이될 것이고, 반대로 3이었다면 "이전 1 2 3 다음"의 형식으로 디스플레이될 것이다. (JSTL 연동)

 

글 목록 정렬 방식 EL

 

다음으로 requset 파라미터에 넣었던 3 개의 Checked들은 위 사진에서 오른쪽에 realtimeChecked, daysChecked, weeksChecked 중 하나에 "checked" 문구가 알맞게 들어가서 디스플레이될 수 있도록 한 것이다.

 

checked가 들어있는 버튼의 색깔이 검정색으로 바뀌는 구조이다.

 


 

글 목록 조회

이제 이번 포스팅의 하이라이트인 글 목록을 조회하는 기능을 어떤 방식으로 구현했는지 보려고 하는데, 일단 아래 사진은 글 목록 조회 시 프론트 컨트롤러에 의해 호출되는 컨트롤러 메서드이다.

글 목록 요청 시 컨트롤러 메서드

 

코드에 대해 설명하자면

 

  1. isExistProfileSession(request) 메서드를 사용해서 현재 요청한 유저가 로그인에 성공한 유저인지 여부를 파악하고 아니라면 첫 페이지로 돌려보냈다. (추후에 이런 요청 선처리는 따로 처리하는 부분을 만들어 관리할 계획이다.)
  2. 세션에서 이전까지 사용하던 글 목록 조회 옵션을 불러왔다.
  3. Request 파라미터로 전달된 글 목록 조회 파라미터를 읽어서 객체에 저장했다.
  4. postService 객체에서 위 두 옵션을 비교하여 현재 요청에 맞는 옵션을 새로 생성했다.
  5. 세션에 생성된 옵션을 저장한다.
  6. 생성된 옵션을 바탕으로 디스플레이할 글 객체(Post)들을 리스트 형태로 불러왔다.
  7. 생성된 글 리스트를 Request 스코프에 넣어줬다.
  8. 맨 아래 부분은 글 리스트를 디스플레이할 때 정렬 방식에 따라 버튼 색상을 바꿔주는 부분이다.

 

새로 적용할 옵션을 구하는 메서드

 

위 사진은 앞서 이야기했던 세션에 저장된 옵션과 리퀘스트에서 불러온 옵션을 적절히 비교하여 새로운 옵션을 생성하는 메서드이다. 이 메서드를 작성할 때 사용자 요청에 대한 시나리오의 측면에서 주요하게 생각한 부분들은 아래와 같다.

 

  • 우선 요청받은 글 정렬 방식과 이전 글 정렬 방식이 둘 다 존재한다면, 정렬 방식이 같던 다르던 상관없이 요청받은 정렬 방식을 기준으로 사용하고, 요청받은 페이지 번호를 사용한다.
  • 요청받은 글 정렬 방식이 없다면 세션에 저장되어있던 정렬 방식을 사용하고, 페이지 번호는 요청받은 것을 사용한다.
  • 만약 요청 방식이나 페이지 번호를 특정할 수 없는 비정상(어브 노말) 상황일 경우엔 페이지 번호 1과 실시간 정렬 방식을 사용한다.

 

그리고 이런 시나리오에 의해 사용자가 원하는(혹은 디폴트) 페이지 번호와 정렬 방식이 정해지면, 페이지네이션 버튼을 구현하기 위해 각 정렬 방 식당 총 페이지 수를 얻어와 변수에 저장하고, setStartEndPageNo(페이지 총 개수, 단위) 메서드를 실행하여 페이지네이션의 시작 번호와 끝 번호를 불러온다.

 

예를 들어 페이지네이션의 시작 번호가 6이고 끝 번호가 10이면 "이전 6 7 8 9 10 다음"과 같은 형식으로 페이지네이션이 생성될 것이다.

 

페이지네이션 시작, 끝 번호 저장 메서드

 

위 사진에서 파라미터 중 하나인 int unit은 단위라는 의미로 작명을 했는데 unit보단 pageCount가 나았을 수도 있겠다.. 아무튼 코드를 설명하자면

 

  1. 우선 현재 이 객체에는 이전 사진의 Service 객체에서 이 객체를 만들 때 생성자로부터 주입받았던 페이지 번호와 정렬 방식이 담겨있다.
  2. 담겨있는 정보를 토대로 페이지 시작 번호를 calcStartNo() 메서드를 사용해 구했다.
  3. 시작 번호와 총 페이지 개수를 토대로 페이지네이션 마지막 번호를 구했다.

 

위 메서드들은 출력을 위한 것이 아닌 객체 자체의 값을 자체 저장하고 있다가 서비스 객체에서 해당 객체를 컨트롤러에 반환해주면 컨트롤러에서 해당 객체를 세션에 등록시켜서 페이지네이션에 사용할 수 있게 구성했고, 아래는 해당 값을 디스플레이해줄 JSP이다.

 

JSP 내부 페이지네이션 부분

 

본격적으로 JSTL을 최대한 깔끔하게 작성해봤다..! 코드를 살펴보면

 

  1. 세션에서 옵션 객체를 불러와서 변수로 저장한다.
  2. 시작, 끝 번호와 현재 페이지 번호를 변수로 저장했다.
  3. <foreach 문>을 사용해 페이지네이션 버튼의 반복을 간결화해주었다.
  4. <if 문>을 사용해 만약 시작 페이지가 1이라면 이전 페이지 버튼(Previous)을 보여주지 않고 아니면 보여줬다.
  5. 페이지네이션 버튼을 반복하는 도중에 해당 페이지가 현재 페이지와 동일하다면 강조색상을 만들어주기 위해 class에 active를 추가했다.
  6. 현재는 페이지네이션 버튼을 5개씩 보여주기 때문에 마지막 번호 - 첫 번호가 4와 같거나 크면 다음 페이지 버튼(Next)을 보여줬다.

 


 

글 상세 조회

맨 처음에 전체적인 페이지 사진을 보면 글 하나당 카드(부트스트랩 컴포넌트로 사진을 포함한 글의 형태) 하나의 형태를 띠고 있는 걸 볼 수 있는데, 거기서 자세히 보기 버튼을 클릭하면 요청에 의해 글 상세 조회가 가능하도록 만들었다. 아래는 글 상세보기 페이지의 와이어프레임과 UI이다.

 

글 상세보기 페이지 와이어프레임

 

글 상세보기 페이지 UI 1

 

글 상세보기 페이지 UI 2

 

글 상세보기 페이지 UI 3

 

위 사진을 보면 와이어프레임과 다른 부분이 한 군데 존재한다. 제목 부분인데 와이어프레임을 보면 제목이 사진과 글 하단에 배치되어있지만 실제 UI를 구성할 때는 제목을 상단에 두었다. 뭔가 css를 계속 고치면서 보는데도 제목이 글 본문보다 하단에 있을 때에 그렇지 않을 때보다 디자인이 이상해 보였기 때문에 제목을 상단으로 위치시키고 나머지는 동일하다.

 

글 상세보기 요청 시 실행되는 컨트롤러 메서드

 

위 코드는 이전에 살펴봤던 글 목록 페이지에서 하나의 글 카드 내부에 있는 자세히 보기 버튼을 클릭하면 서버로 전송되는 요청에 대한 처리 컨트롤러 메서드다. 

 

메서드 처리 과정은 다음과 같다.

 

  1. Request 객체에 저장된 파라미터 중 "no"(글 번호라는 의미)의 키 값을 갖는 파라미터를 불러온다.
  2. 서비스 객체의 메서드 중 전달받은 글 번호 파라미터를 인자로 하는 getPost(no) 메서드를 실행하여 Post 객체 하나를 불러온다. 이때 만약 객체를 찾지 못했다면 null을 반환한다.
  3. 만약 null이 반환되었다면 글 목록 페이지로 돌아갈 수 있도록 메인 페이지로 리다이렉트 해준다.
  4. 해당 글에 달린 댓글 리스트를 서비스 객체의 getComments() 메서드를 사용해서 불러와 Request에 저장한다.
  5. 글 상세보기 페이지에 접속하는 유저의 번호를 세션 스코프에서 알아내서 현재 유저가 해당 글에 좋아요 버튼 클릭을 했는지 알 수 있도록 boolean 값을 받아온다.
  6. 받아온 isLike라는 값을 Request에 저장하고 글 상세보기 페이지 정보를 갖는 ViewInfo 객체를 반환해준다.

 

이 과정에서 DB에 좋아요 번호, 글 번호, 계정 번호를 갖는 Likes라는 테이블을 생성했다. 옛날에 프로젝트를 설계할 때 이런 부분까지 고려가 되었다면 좋았을 텐데 뭔가 기술적으로는 많이 고려되지 않고 기획적인 측면에서 더 많이 고려해서 이렇게 된 것 같다. 새로 만든 테이블은 단순히 어떤 글에 누가 좋아요를 눌렀는가에 대한 정보만을 담고 있다.

 

댓글 게시 요청 시 처리되는 컨트롤러 메서드

 

위 코드는 댓글 게시 요청을 처리해주는 컨트롤러 메서드 코드이다. 댓글 게시하기 버튼을 클릭하기 전에 체크박스를 체크하면 isUseAnonymousName에 true가 저장되어 추후 다른 유저가 글 상세보기 페이지에 접속하더라도 닉네임이 표시되지 않고 익명으로 표시된다.

 

Request에서 받아오는 다른 파라미터들은 댓글 내용, 글 번호, 게시자 계정 번호로 해당 정보를 토대로 서비스 객체의 createComment() 메서드를 사용하면 comment 테이블에 insert 될 수 있도록 설정해두었다.

 

댓글(comment)이나 좋아요(likes) 테이블은 새로 튜플이 추가되거나 하면 그에 맞는 글(post)의 좋아요 개수와 댓글 개수를 직접적으로 변환시켜야 한다. 

 

그래서 처음엔 addLike(), delLike(), addComment(), delComment(), addLikeCount(), delLikeCount(), addCommentCount(), delCommentCount() 이런 메서드들을 따로 만들고 나름 간략화 등의 작업을 하며 구현을 해놓았었다.

 

그런데 서버를 실행시켜서 몇 번의 테스트를 실시하는 과정에서 좋아요나 댓글 개수가 0인데 del~~Count() 메서드가 실행되거나 하는 문제가 발생한 적이 있다. 물론 코드에 원래 문제가 있어서 발생한거지 갯수 증가/감소 메서드를 따로 작성했다고 발생한 문제는 아니긴 했다. 

 

그렇지만 만약 DB를 사용하는 도중에 addLike() (likes 테이블에 정보를 등록하는 메서드)는 성공했지만, addLikeCount() (post 테이블에서 likeCount를 증가시키는 메서드)가 모종의 이유로 실패하는 일이 발생한다면 정확한 갯수 측정이 되지 않을 뿐만 아니라 시스템 오류를 야기할 수 있다고 생각했다.

 

이런 오류들을 줄이고자 댓글이나 좋아요의 개수를 증가/감소하는 메서드는 모두 삭제했고, 대신에 실시간 값을 읽어서 반영하는 메서드를 아래 사진의 하단과 같이 추가했다.

 

좋아요 추가/해제 시 호출되는 서비스 객체의 메서드

 

유저가 좋아요 버튼을 클릭하면 좋아요가 추가될 수도 있고 이미 좋아요를 추가한 상태라면 취소될 수도 있다. 그렇기에 isAlreadyLikeThis() 메서드를 사용하여 해당 글에 유저가 이미 좋아요를 한 전적이 있는지 조회하고 addLike(), delLike() 메서드 중 하나를 택하여 실행시키면 likes 테이블(DB)에 유저와 글에 대한 번호가 저장된다.

 

마지막으로 반영된 좋아요에 대하여 서비스 객체에서 Dao를 호출하여 likes 테이블에서 해당 글에 달린 좋아요 개수를 가져오는 메서드인 getLikeCount()를 실행하여 좋아요 갯수를 가져오고 updateLikeCount() 메서드를 사용해 좋아요 갯수를 적용시키는 것이다.

 

서비스 객체에서 댓글을 생성해주는 메서드

 

위 사진은 유저로부터 입력받은 댓글을 등록해주는 메서드로 실행 순서는 다음과 같다.

 

  1. 댓글이 입력될 글 번호, 세션에 저장되어있던 프로필 정보, 댓글 내용 중 하나라도 null 이면 메서드는 수행되지 않고 댓글이 생성되지 않은 채로 종료된다.
  2. isUseAnonymousName 파라미터가 "on"이라면 isAnonymous(boolean 값)을 true로 바꾸어 commentDao에서 true로 저장할 수 있게 해 준다.
  3. try문을 사용하여 DB 저장 혹은 int 변환 중 오류가 발생하더라도 프로그램이 뻑나는 현상을 막아준다.
  4. 만약 제대로 댓글이 등록되지 않았다면 댓글이 추가되지 않은 채로 메서드는 종료될 것이다.
  5. 제대로 등록이 되었다면 postDao의 getCommentCount()와 updateCommentCount() 메서드를 사용하여 변경 사항을 적용해준다.

 


 

마치며

이번에는 메인 페이지, 글 목록 조회 기능, 글 상세보기 페이지 기능들에 대해 포스팅했는데, 아직 고칠 점도 많고 구조적으로 개선되어야 할 항목들도 보이는 상황이다. 포스팅은 늦었으나 매일 잔디를 심고 있고... 모쪼록 더욱 열심히 해야겠다! 얼른 스프링 프레임워크를 도입해야 하기 때문이다 ^^

 

감사합니다!!

반응형