게시판 만들기 #7 / Read

jh_leitmotif·2021년 7월 24일
0
post-thumbnail

🧐 개요

Node.js CRUD 게시판 생성을 정리합니다.

이번 포스트는 Read입니다.

📋 게시판 html

간단히 작성한 page.html입니다.
가독성을 위해 일부 내용은 제거하여 작성합니다.

<!DOCTYPE html>
<html>
  <head>
    <title><%= title %></title>
    <link rel='stylesheet' href='../../css/style.css' />
  </head>
  <body>
    <div class="ContentField" style="text-align:center">
      <h1><%= title %></h1>
      <table>
        <tr>
          <th>번호</th>
          <th>제목</th>
          <th>글쓴이</th>
          
        </tr>
        <%
        for(var i = (page * page_num) - page_num; i < (page * page_num); i++) {
          if (i>length){
            break; // i가 데이터 갯수보다 많아지면 반복문을 나간다.
          }else{
                  // 그렇지 않다면 데이터를 지속적으로 보낸다.
          
        %>
        <tr>
          <td>
            <%=rows[i].idx%>
          </td>
          <td><a href='post/<%=rows[i].idx%>'><%=rows[i].title%></a></td>
          <td><%=rows[i].nick%></td>
        </tr>
        <%}
        }
        %>
        <tr>
          <td colspan="3">
              <%
              for(var j = 0; j < rows.length / page_num; j++){
              %>
              [<a href="/board/list/<%= j + 1 %>"><%= j + 1 %></a>]
              <%
              }
              %>
          </td>
      </tr>
      </table>
      <br>
      <a href="../write/<%=lastidx%>" style="margin-top:50px"><input type="button" value="글 쓰기"></a>
    </div> 
  </body>
</html>

포스트의 숫자가 10개가 넘어가면 페이징이 적용되도록 되어있습니다.

또한 테이블 컬럼이 번호, 제목, 글쓴이의 3개로 이루어져 있으므로

만약 테이블에 3개 미만의 값이 들어가는 경우,
태그에 colspan="3"을 지정해 병합을 진행했습니다.

여기에 주목할 것은 ASP.NET 인라인 식과 페이징입니다.

ASP.NET 인라인 식

ASP.NET은 MS가 개발한 웹 애플리케이션 프레임워크이며 프로그래머들이 동적인 웹 사이트, 웹 애플리케이션, 웹 서비스를 만들 수 있게 도와 준다. 위키백과

html 페이지에 있는 <%=~%>, <%~%> 은 ASP.NET 프레임워크에서 제공하는 인라인 식입니다.

  1. <% ~ >
    프로그래밍 문장 또는 call 함수를 사용합니다.
    본 html에서는 전달받은 마지막 포스트 ID를 통해 페이징 로직을 적용했습니다.
  2. <%= ~ % >
    단일 문자열, 변수, 상수 등을 표시하는 방법입니다.
    이를 통해 각 포스트의 번호, 제목, 글쓴이 및 페이징 번호를 표시했습니다.

이 외 4가지의 인라인식이 더 있습니다. 참고 링크를 아래에 남깁니다.

  1. MS 공식 홈페이지 :
    https://docs.microsoft.com/ko-kr/troubleshoot/aspnet/inline-expressions
  2. MS 블로그 :
    https://weblogs.asp.net/scottgu/new-lt-gt-syntax-for-html-encoding-output-in-asp-net-4-and-asp-net-mvc-2

✔ 페이징

단순히 포스트를 나열하는 경우 갯수가 많으면 많을 수록 화면이 복잡해집니다.

따라서 사용자의 직관성 개선을 위해 포스트를 10개씩만 제한하여 표시합니다.

for(var i = (page * page_num) - page_num; i < (page * page_num); i++) {
     if (i>length){
       break; // i가 데이터 갯수보다 많아지면 반복문을 나간다.
     }else{
        ...     // 그렇지 않다면 데이터를 지속적으로 보낸다.
        포스트[i];
     }
}

for(var j = 0; j < rows.length / page_num; j++){
    [<a href="/board/list/<%= j + 1 %>"><%= j + 1 %></a>]
} 
                  

page : 사용자가 선택한 페이징 번호
page_num : 출력할 포스트의 갯수. 10개로 고정
length : 포스트의 총 갯수. Array는 0부터 시작하므로, 총 갯수-1 이 전달됨.
rows : 포스트들의 정보를 DB에서 얻어온 것

페이징을 간단하게 정리하면 다음과 같습니다.

페이징 번호를 n이라고 할 때
(n ✖ 10 - 10) 번부터 (n ✖ 10) 번 까지의 포스트를 출력한다.
단, 남은 포스트번호가 (n ✖ 10) 미만인 경우를 방지해야한다.

그러므로 따로 어려운 것 없이, 그대로 for문으로 작성하고

만약 남은 포스트가 없는 경우 for문을 강제로 끝내면 됩니다.

페이징 번호는 (총 갯수)➗(출력할 갯수)로 구해 html에 출력합니다.

<a href="/board/list/<%= j + 1 %>"><%= j + 1 %></a>

앞서 언급한 인라인식을 통해 선택한 페이징 번호를 전달합니다.

완성된 html 화면입니다.


📋 게시판 접속 라우터 - 1

router.get('/board/list',function (req,res,next) {
  res.redirect('/board/list/1')
  // /board로 접속요청이 들어왔을 때 1페이지로 자동으로 이동하도록 리다이렉트 해줍니다.
  req.session.refresh = true; 
  // 무한 새로고침으로 조회수 폭증을 막는 세션 값
})

사용자가 정상적으로 게시판 버튼을 클릭했을 때 사용됩니다.

곧장 1번 페이징의 게시판 화면으로 보내줍니다.

req.session.refresh는 조회수 폭증을 막는 값으로 임의로 설정해둔 것으로

후술할 작성글 접속 라우터 부분에서 정리합니다.


📋 게시판 접속 라우터 - 2

router.get('/board/list/:page', function(req, res, next) {
  req.session.refresh = true;
  const page = req.params.page;
  const sql = 'SELECT idx, nick, title, content, hit FROM board ORDER BY idx DESC'; 
  // 페이징 포스트가 최근 작성된 것부터 보이도록 DESC 처리.
  conn.getConnection((err,connection)=>{
    if (err) throw err;
    const query = connection.query(sql,function(err,rows){
      if (err) throw err;
      if (rows.length==0){
        const idxInit = 'ALTER TABLE board AUTO_INCREMENT=1;'
        const idxInitQuery = connection.query(idxInit,function(err,rows){
          if (err) throw err;
        })
        res.render('boardHTML/pageEmpty.html',{title:'게시판 리스트',lastidx:1});
      }else{
        res.render('boardHTML/page.html',{title:'게시판 리스트',rows:rows, page:page, length:rows.length-1, page_num:10, lastidx:rows[0].idx+1});
      }
      connection.release();
    })
  })
});

페이징 번호를 클릭했을 때 거쳐가는 라우터입니다.

ID 컬럼 기준으로 내림차순으로 정렬시킨 조회 결과 값을 통해
게시판이 최신글부터 확인되도록 했습니다.

특이사항으로는 포스트가 아예 없는 경우에 대한 분기입니다.

글이 없는데 페이징 로직이 실행된다던가..하는 데에서 오는 비효율성을 막고
혹시 모를 undefined 오류방지를 위해 pageEmpty.html을 렌더링합니다.

또한 포스트가 저장되는 board 테이블의 AUTO_INCREMENT를 1로 초기화하여
다음에 작성되는 글이 1부터 시작하도록 만들었습니다.

pageEmpty.html은 page.html과 구조가 모두 똑같지만
스크립트가 모두 배제되어있고, 글쓰기 버튼만 활성화되어 있습니다.

대략 아래와 같은 화면으로 구성되어 있습니다.

번호	제목	글쓴이
작성된 글이 없습니다 ㅠㅠ
			글쓰기

📋 작성글 html

<!DOCTYPE html>
<html>
  <head>
    <title><%= title %></title>
    <link rel='stylesheet' href='../../../css/style.css' />
  </head>
  <body>
    <div class="ContentField">
      <table frame=void>
        <%
        for(var i = 0; i<rows.length; i++)
        {
        %>
        <tr>
          <td colspan="2"><h1><strong><%=rows[i].title%></strong></h1></td>
        </tr>
        <tr>
          <td><%=rows[i].nick%> 조회수: <%=rows[i].hit%> 추천수: <%=rows[i].recommend%></td>
        </tr>
        <%
        }
        %>
        <tr>
          <td style="text-align:right;">
            <a href="/board/list"><input type="button" value="목록"></a>
            <a href="../../update/<%=rows[0].idx%>"><input type="button" value="글 수정"></a>
            <a href="../../delete/<%=rows[0].idx%>"><input type="button" value="글 삭제"></a>
          </td>
        </tr>
      </table>
    </div>
  </body>
</html>

html파일은 다음과 같이 3가지로 나뉘어져 있습니다.

  1. 글 작성자
  2. 추천을 누른 방문자
  3. 추천을 누르지 않은 방문자

위의 html은 1번에 해당되는 예시입니다.

당연히 자기 자신의 post에 대한 추천을 누르면 안되므로
글 수정, 삭제 버튼만 존재합니다.

반대로 2번의 경우, 글 수정 및 삭제 버튼 대신 추천 버튼이 있고
3번은 추천 해제 버튼이 있습니다.

원본에는 이미지 파일을 보이게, 보이지 않게 하는 부분이 있으나
따로 정리하기 위해 여기에선 삭제했습니다.

📋 작성글 접속 라우터

router.get('/board/list/post/:page',function(req,res,next){
  const page = req.params.page;
  conn.getConnection((err,connection)=>{
    if (err) throw err;
    const query = connection.query('SELECT idx, title, name, nick, content, hit, recommend, uploadfilepath FROM board where idx='+page,function(err,rows){
      if (err) throw err
      const author = rows[0].name;
      const preRows = rows;
      if (author==req.session.displayName){
        res.render('boardHTML/postUser.html',{title:rows[0].title, rows:rows, fileName:fileArray,imgPaths:imgFilePath});
      }else{
        const recomSelSql = 'SELECT recomPost FROM users WHERE id=?';
        const recomSelQuery = connection.query(recomSelSql,[req.session.displayName],(err,rows)=>{
          if (err) throw err;
          if (rows.length==0 || rows[0].recomPost==null){
            res.render('boardHTML/postGuest.html',{title:preRows[0].title,rows:preRows,fileName:fileArray,imgPaths:imgFilePath});
          }else{
            const recomPosts = rows[0].recomPost;
            const recomPostsArray = recomPosts.split(';');
            if (recomPostsArray.includes(page)){
              res.render('boardHTML/postGuestRecommended.html',{title:preRows[0].title,rows:preRows,fileName:fileArray,imgPaths:imgFilePath});
            }else{
              res.render('boardHTML/postGuest.html',{title:preRows[0].title,rows:preRows,fileName:fileArray,imgPaths:imgFilePath});
            }
          }          
        })
      }
      if (req.session.refresh==true){
        const query = connection.query('UPDATE board SET hit=hit+1 WHERE idx='+page, function(err,rows){
          if (err) throw err;
        })
      }
      connection.release();
      req.session.refresh=false; // 조회수 증가 후에는 새로고침 세션 값을 false로 돌려 post에서 update가 발생하지 않도록 방지한다.
    })
  })
})

작성된 글에 접근했을 때 거치는 라우터입니다.

원본에는 첨부된 파일 중 이미지 확장자 파일만 걸러내어
해당 파일들의 경로를 html로 전달하는 로직이 있으나
가독성 향상 및 별도 정리를 위해 여기에선 삭제했습니다.
<a href='post/<%=rows[i].idx%>'><%=rows[i].title%></a>

게시판 화면에서, 제목을 클릭하면 포스트에 접근할 수 있습니다.
또한 해당 글의 ID를 서버에 파라미터로 전달합니다.

우선 현재 사용자가 글의 작성자인지 확인하고, 맞다면 그에 맞는 페이지를 렌더링합니다.

글의 작성자가 아니라면 방문자로 취급한 분기로 넘어갑니다.

user 테이블엔 사용자들이 추천한 글의 ID를 모아둔 recomPost 컬럼이 있습니다.

해당 컬럼 값 중, 현재 접근하려고 하는 글의 ID가 포함되어있는지 확인하여

포함되어 있다면 추천 해제 버튼이 있는 html,
포함되어 있지 않다면 추천 버튼이 있는 html로 렌더링합니다.

구조는 다음과 같습니다.

✔ 조회수 폭증 방지

여러 커뮤니티나 카페를 보면, 조회수나 추천수를 기반으로
인기글을 따로 모아두거나 하는 경우가 있습니다.

그 중 간혹 발생하는 것이 새로고침을 통한 조작인데요,

그것을 미리 방지하는 세션 값을 심어두었습니다.

req.session.refresh

해당 값의 초기화 위치가 곳곳에 분포되어있어, 부득이 구조도로 표현합니다.

  1. page.html 접근시 refresh = true로 초기화
  2. post.html 접근
    2-1. 만약 refresh값이 true인 경우 조회수 증가, refresh를 false로 초기화
    2-2. 만약 refresh값이 false인 경우 조회수는 증가하지 않음.

위와 같이 하면 처음 포스트에 접근했을 때만 조회수가 증가하고

이후 새로고침을 해도 refresh값이 false이기 때문에 조회수가 증가하지 않습니다.


🙄 후기

추천, 추천해제 라우터는 그저 전달받은 ID를 통해 DB 값을 검사하고

값을 증가시키거나, 또는 감소시키는 것뿐이라 작성하지 않았습니다.

다음 작성할 Update에서는 드디어 async await로 구성시켜둔 부분이 나옵니다.

profile
Define the undefined.

0개의 댓글