Side Project/STUHEL (학습을 돕는 예약시스템)

Toy-project 개발일지 ▲NO.16 ▲ 게시판 만들기(3) - 게시글 댓글달기, 게시글 읽기/ Spring-boot, Oracle, Java, JavaScript, HTML, CSS부트스트랩, 토이프로젝트

isjiji 2022. 9. 6. 15:42

단순히 게시된 글을 읽는 기능만 한다면, 어렵지 않았지만

댓글기능까지 추가하는 바람에 (처음엔 손쉽게 할줄알았지만) 꽤나 좌충우돌했다^^

'나 이제 좀 개발에 익숙한걸?' 싶으면 고비가 찾아온다. 흑흑 

 

 

개발사항은

1. 글을 읽는 사람이 작성자 아닌경우만 조회수증가

2. 게시판 목록에서 클릭한 글 조회 및 화면에 데이터 뿌리기

3. 댓글/대댓글 기능구현

 

 

어려웠던 문제는

1. 공통단에서 sessionData를 받아 올 수 있느냐 마느냐.

2. viewCount 사용자-작성자 검사시 session에 저장된 데이터를 어떻게 활용할것이냐

3. 댓글/대댓글 기능 구현 그자체 

댓글 대댓글기능인 다음게시글에서 확인 가능!!

 

 

게시글목록에서 글 선택 ☞ 글 읽기 화면/댓글

 

 

게시판읽기/ 댓글 HTML

게시판 읽기 화면 또한 TISTORY가 기본제공하는 게시판 화면처럼 단순하게 만들었다. 

<section style="padding-top:10rem; padding-bottom: 10rem;resize: none;position:relative;">
    <!--제목-->
    <div style="display: flex; justify-content: center;position:relative;">
        <div style="width:700px;position:relative" >
            <div align="center">
                <h1 id="title" style="text-align:center"></h1>
            </div>
        </div>
    </div></br>
    <div style="position:relative;display: flex; justify-content: center;">
        <div style="width:900px;position:relative">
        	<!--작성정보-->
            <div>
                <span id="wroteDate" align="right" style="color=#D5D5D5; font-size:14px"></span>&nbsp;&nbsp;·&nbsp;&nbsp;
                <span id="writer" align="left" style="color=#D5D5D5; font-size:14px"></span>
            </div>
            <hr>
            <!--글내용-->
            <div align="center">
                <textarea required readonly id="note" style="outline:none;height:400px; padding:20px ;resize:none; width:900px;border:none;font-size:1.2rem;"></textarea>
            </div>
            <!--댓글-->
            <div>
                <div id="commentDiv" margin="0.7rem">
                </div>
                <div margin="20rem" style="padding=10rem;">
                    <textarea placeholder=" 댓글을 입력하는 당신, 센스쟁이 우후훗" id="commentTxt" style="resize: none;height:100px; width:900px;font-size:0.9rem;" ></textarea>
                    <input type="button" id="saveComment" value="댓글달기" onclick="saveCommentFunction('comment')" class="btn btn-primary btn-xl" style="font-size:15px;padding:11px 22px;text-align:center">
                </div>
            </div>
        </div>
    </div>
</section>

 

 

BoarRead에서 중요한 점은 바로!!

boardList에서 글제목을 클릭!하면 boardRead에 들어가기 전에 먼저 서버를 조회해 글정보를 가져오는것이다.

그리고 그 정보를 location.href로 파라미터에 담아서 boardRead에 보낸다.

 

List에서 굳이 Read에 담을 데이터를 조회한 이유는, List가 Read를 조회할 때 필요한 정보를 가지고 있기 때문이었는데 지금와서 로직을 보니, boardList가 가지고 있는 데이터를 일단 boardRead에 넘겨준 다음 boardRead에서 글을 조회해도 될것 같다. 뭐가더 효율적일지는 조금더 생각해봐야겠다.

 

 

 

아래는 baordList에서 글 클릭하면 실행되는 js로직

//글 제목링크 클릭 / 글 내용확인창
function retrieveBoardRead(userSelectedRowData){
    if(userSelectedRowData==null||userSelectedRowData==""){
        return;
    }
    console.log('userSelectedRowData');
    console.log(userSelectedRowData);
    userSelectedRowData = JSON.stringify(userSelectedRowData);
    let xhr = new XMLHttpRequest();
    xhr.open('GET', "/stuhel/board/retrieveBoardRead?boardData="+encodeURI(userSelectedRowData)
        ,true);
    xhr.setRequestHeader('Accept', 'application/json');
    xhr.send();
    xhr.onreadystatechange = () => {
        if (xhr.readyState == 4 && xhr.status == 200) {
            let txt = xhr.responseText;
            //Read페이지로 조회정보 넘김
            location.href="/stuhel/board/boardRead.html?boardReadData="+encodeURI(txt);
        }
    }
}

 

 

글 클릭 Controller

gson으로 데이터를 변환하고 TO에 담은 다음 Service단으로 보내고, 조회된 데이터를 view로 넘긴다.

@GetMapping("/retrieveBoardRead")
String retrieveBoardRead(@RequestParam("boardData")String boardData){
    BoardTO board = gson.fromJson(boardData, BoardTO.class);
    return gson.toJson(boardService.retrieveBoardRead(board));
}

 

 

글 클릭 ServiceImpl

Mapper에서 조회한 데이터 중 note 에 내려쓰기를 한다.

글들을 그냥 DB에저장하고 불러오면 내려쓰기가 되지 않아서, 

글이 저장될 때, 내려쓰기된 \n\r 을 전부 <br/> 로 표식을 변경했다. 

글이 출력될 때 유효한 내려쓰기를 위해 <br/>을 \n으로 변경해주었다.

@Override
public BoardTO retrieveBoardRead(BoardTO boardTO) {
    BoardTO board= boardMapper.selectReadBoard(boardTO);
    board.setNote(board.getNote().replaceAll("<br/>","\n")); //유효 엔터키로 변경
    return board;
}

 

 

글 클릭 Mapper

<select id="selectReadBoard" resultMap="Board" parameterType="map">
    SELECT *
    FROM BOARD
    WHERE NOTE_SEQ = #{noteSeq}
    AND WRITER = #{writer}
    AND TITLE = #{title}
    ORDER BY NOTE_SEQ ASC
</select>

 

 

 

최근 리팩토링하면서

반복되는 로직은 공통단(common.js)를 만들어서 로직재사용성을 높이고자했다.

그래서 즉시실행함수로 session을 확인하는 로직도 따로 빼주었다.

하지만 BoardRead 의 경우 댓글을 달거나 조회수체크를 할 때 session데이터를 사용하기 때문에 공통단의 session체크로직을 사용하지 않고 boardRead만의 session확인 로직을 사용했다.

(공통단으로 받은 데이터가 왜 안넘어오는지 몰라.. 방법만 찾으면 공통단로직이 사용되게 하고싶다. 누구 가르쳐줄사람있나요? 전 이유를 못찾았습니다..)

 

 

암튼 List에서 게시글 클릭한 다음,

boardRead 화면에 들어오면 제일 먼저 boardList에서 보낸 정보를(글, 제목, 작성자, seqNumber등) 화면에 뿌린다.

여기서 내려쓰기 변경을 한 번 더 해준다. 리팩토링하면서 JavaScript로 각종 기능을 입혀뒀어도 업무로직이 있는 Service단에서 한번더 확인/변경을 해주고자 했다. 동일한 서비스 로직이 반복되기 때문에 속도가 느려지거나 레거시라고 생각될 수도 있지만, 

서버 로직이 변경될 수도 있고, script로직이 변경될 수도 있기 때문에 최대한 이중으로 막아두는 것이 좋다고 생각했다.또한 Service단에서 업무로직이 작성되어야하기에 연습삼아 굳이 두번씩 점검해주었다.

 

 

글내용과 정보를 화면에 뿌려주는것만으로 Read의 가장 중요힌 역할은 끝이났다! 

하 지 만~ 게시글 기능에서 빠질 수 없는 조회수, 댓글 기능이 아직 남아있다. 두둥 !조회수체크는 간단하지만, 댓글기능은 다룰 내용이 살짜쿵 많기에 다음 게시글에 작성하겠다 !!!

 

 

 

데이터 화면에 뿌리기 js

<!--초기세팅-->
let sessionStatus = null;
(function(){
    let xhr = new XMLHttpRequest();
    xhr.open('Get', "/session/sessionCheck"
             ,true);
    xhr.setRequestHeader('Accept', 'application/json');
    xhr.send();
    xhr.onreadystatechange = () => {
        if (xhr.readyState == 4 && xhr.status == 200) {
            // 데이터 확인
            sessionStatus = xhr.responseText;
            if(sessionStatus==null||sessionStatus==''){
                location.href="/index.html";
                return;
            }else if(sessionStatus!=null){
                <!--게시글 목록에서 보낸 파라메터 정보 받음-->
                sessionData = JSON.parse(sessionStatus);
                let url = new URL(window.location.href);
                let urlParams = url.searchParams;
                boardReadData = JSON.parse(urlParams.get("boardReadData"));
                <!--데이터 화면에 뿌리기-->
                wroteDate.innerHTML=boardReadData.writeDate;
                writer.innerHTML=boardReadData.writer;
                title.innerHTML=boardReadData.title;
                note.value=boardReadData.note.replaceAll("<br/>", "\r\n");  //내려쓰기 변경
                noteSeq=boardReadData.noteSeq;

                textareaResize(note);
                viewCount();
                retrieveBoardComment();
            }
        }
    }
})();

 

 

글내용창 높이 설정하기

textarea의 기본 높이는 400px로 지정해놨다. 

400px보다 더 긴 글이 들어올 것을 대비해 textarea에서 keyup이벤트가 발생하면 textareaResize()함수를 호출해 textarea의 scroll길이에 맞춰서 textarea의 높이를 지정해주도록했다.

<!--글 내용길이에 따라 textarea길이 설정-->
function textareaResize(obj) {
    if(obj.scrollHeight>=400){
        obj.style.height = (obj.scrollHeight)+"px";
    }else obj.style.height = "400px";
}

 

 

조회수 올리기

Read화면에 들어오면, 곧장 조회수올리기 viewcount함수가 실행된다. 

단! 자기가 자기 글을 읽을 때는 조회수가 올라가지 않게하기 위해 if문으로 사용자 정보인 session데이터의 user ID와 글 작성자인 writer ID 를 비교했다.

조회수측정중 에러가 발생하더라도 Read화면에서는 딱히 에러를 띄우지 않기로했다.

그 이유는 조회수가 중요하더라도 경고문을 띄울만한 조건은 아니라 판단했기 때문이다.

<!--조회수-->
function viewCount(){
    if(boardReadData.writer==sessionData.id){
        return;
    }else{
        let boardData = Object();
        <!--조회수 올릴 글 조건검사를 위한 데이터-->
        boardData.title=boardReadData.title;
        boardData.writer=boardReadData.writer;
        boardData.noteSeq=boardReadData.noteSeq;
        boardData.reader=sessionData.id;

        boardData=JSON.stringify(boardData);
        console.log(boardData);

        let xhr = new XMLHttpRequest();
        xhr.open('PATCH', '/stuhel/board/addViewCount?boardData='+encodeURI(boardData)
                 ,true);
        xhr.setRequestHeader('Accept', 'application/json');
        xhr.send();
        xhr.onreadystatechange = () => {
            if (xhr.readyState == 4 && xhr.status == 200) {
                // 데이터 확인
                let errorInfo = xhr.responseText;
                console.log(errorInfo);
            }
        }
    }
}

 

 

조회수 Controller

데이터가 변경되기 때문에 Patch!!!!!!!

@PatchMapping("/addViewCount")
int addViewCount(@RequestParam("boardData")String boardData){
    BoardTO board = gson.fromJson(boardData, BoardTO.class);
    return boardService.addViewCount(board);
}

 

 

조회수 ServiceImpl

서비스단에 오면, 글의 작성자와 글을 읽는 사람이 동일한 ID를 가졌는지 확인한다.

작성자가 자기글을 읽을 경우에는 조회수가 올라가지 않게 만들기 위해서다.

작성자와 독자가 같으면 메서드는 종료되며 -1이 return된다. 

 

작성자와 독자가 다른 사람일 경우 viewCount를 업데이트한다. 

여기서 결과에 따라 반환되는 returnInfo 0 혹은 -1은 사실 view에서 아무런 작동을 하지 않는다. 

에러 확인을 위해 임시로 작성해두었다. 

@Override
public int addViewCount(BoardTO board) {
    int returnInfo;
    //작성자와 독자(사용자) 동일 검사
    if(board.getWriter().equals(board.getReader())){
        return -1;
    }
    try {
        boardMapper.updateViewCount(board);
        returnInfo=0;
    }catch (Exception e){
        e.printStackTrace();
        returnInfo=-1;
    }
    return returnInfo;
}

 

 

조회수 Mapper

click_amount 컬럼의 데이터가 1 증가한다.

<update id="updateViewCount" parameterType="com.helper.study.stuhel.board.to.BoardTO">
    UPDATE BOARD
    SET CLICK_AMOUNT = CLICK_AMOUNT+1
    WHERE
        NOTE_SEQ=#{noteSeq}
        AND TITLE=#{title}
        AND WRITER=#{writer}
</update>