커뮤니티 - 댓글 작성 기능 구현 (23.07.21)

·2023년 7월 21일
0

Server

목록 보기
26/35
post-thumbnail

📝 댓글 작성 기능


💡 VS Code

🔎 reply.js

어제 댓글 목록 조회할 때 만들어 놓은 selectReplyList() 함수를 활용하면
댓글을 등록하자마자 새로고침을 하지 않아도 내가 작성한 댓글이 바로 화면에서 업데이트 된다!

// selectReplyList();

// 댓글 목록 조회(AJAX)
function selectReplyList(){

    // contextPath, boardNo, memberNo 전역 변수 사용
    $.ajax({

        url : contextPath + "/reply/selectReplyList",
        data : {"boardNo" : boardNo},
        type : "get",
        dataType : "JSON", // JSON 형태의 문자열 응답 데이터를 JS 객체로 자동 변환
        success : function(rList){
            // rList : 반환받은 댓글 목록
            console.log(rList);

            // 화면에 출력되어 있는 댓글 목록 삭제
            const replyList = document.getElementById("reply-list"); // ul태그
            replyList.innerHTML = "";

            // rList에 저장된 요소를 하나씩 접근
            for(let reply of rList){
                
                // 행
                const replyRow = document.createElement("li");
                replyRow.classList.add("reply-row");

                // 작성자
                const replyWriter = document.createElement("p");
                replyWriter.classList.add("reply-writer");

                // 프로필 이미지
                const profileImage = document.createElement("img");

                if(reply.profileImage != null){ // 프로필 이미지가 있는 경우

                    profileImage.setAttribute("src", contextPath + reply.profileImage);

                } else{ // 없는 경우 == 기본 이미지
                    profileImage.setAttribute("src", contextPath + "/resources/images/user.png");
                }

                // 작성자 닉네임
                const memberNickname = document.createElement("span");
                memberNickname.innerText = reply.memberNickname;

                // 작성일
                const replyDate = document.createElement("span");
                replyDate.classList.add("reply-date");
                replyDate.innerText = "(" + reply.createDate + ")";

                // 작성자 영역(p)에 프로필, 닉네임, 작성일 마지막 자식으로 추가
                replyWriter.append(profileImage, memberNickname, replyDate);

                // 댓글 내용
                const replyContent = document.createElement("p");
                replyContent.classList.add("reply-content");

                // 왜 innerHTML? <br> 태그 인식을 위해서
                replyContent.innerHTML = reply.replyContent;

                // 행에 작성자, 내용 추가
                replyRow.append(replyWriter, replyContent);

                // 로그인한 회원 번호와 댓글 작성자의 회원 번호가 같을 때만 버튼 추가
                if(loginMemberNo == reply.memberNo){

                    // 버튼 영역
                    const replyBtnArea = document.createElement("div");
                    replyBtnArea.classList.add("reply-btn-area");
                    
                    // 수정 버튼
                    const updateBtn = document.createElement("button");
                    updateBtn.innerText = "수정";
    
                    // 삭제 버튼
                    const deleteBtn = document.createElement("button");
                    deleteBtn.innerText = "삭제";
    
                    // 버튼 영역 마지막 자식으로 수정/삭제 버튼 추가
                    replyBtnArea.append(updateBtn, deleteBtn);

                    // 행에 버튼 영역 추가
                    replyRow.append(replyBtnArea);
                }
                    
                // 댓글 목록(ul)에 행(li) 추가
                replyList.append(replyRow);
            }
        },

        error : function(){
            console.log("에러 발생");
        }
    });
}

// -----------------------------------------------------------------------------------------------

// 댓글 등록
const addReply = document.getElementById("addReply");
const replyContent = document.getElementById("replyContent");

addReply.addEventListener("click", function(){ // 댓글 등록 버튼이 클릭이 되었을 때

    // 1) 로그인이 되어 있니? -> 전역변수 loginMemberNo 이용
    if(loginMemberNo == ""){ // 로그인 X
        alert("로그인 후 이용해 주세요.");
        return;
    }

    // 2) 댓글 내용이 작성되어 있니?
    if(replyContent.value.trim().length == 0){ // 미작성인 경우
        alert("댓글을 작성한 후 버튼을 클릭해 주세요.");

        replyContent.value = ""; // 띄어쓰기, 개행문자 제거
        replyContent.focus();
        return;
    }

    // 3) AJAX를 이용해서 댓글 내용 DB에 저장(INSERT)
    $.ajax({
        url : contextPath + "/reply/insert",
        data : {"replyContent" : replyContent.value,
                "memberNo" : loginMemberNo,
                "boardNo" : boardNo},
        type : "post",
        success : function(result){

            if(result > 0){ // 등록 성공
                alert("댓글이 등록되었습니다.");
                
                replyContent.value = ""; // 작성했던 댓글 삭제
                selectReplyList(); // 비동기 댓글 목록 조회 함수 호출

            } else{ // 실패
                alert("댓글 등록에 실패했습니다..");
            }
            
        },
        error : function(req, status, error){
            console.log("댓글 등록 실패")
            console.log(req.responseText);
        }
    });
})

💡 Eclipse

🔎 Util.java

  1. 개행 문자 -> br 변경 메서드 (줄바꿈)
  2. Cross Site Scripting(XSS, 크로스 사이트 스크립팅) 공격 방지 처리 메서드

자주 쓰이는 기능들을 static method로 만들어 계속 재사용하도록 하겠다.

package edu.kh.community.common;

public class Util {

	// 개행 문자 -> <br> 변경 메서드
	public static String newLineHandling(String content) {
		
		return content.replaceAll("\r\n|\n|\r|\n\r", "<br>");
		
		// textarea의 엔터 : \r\n
		// \r : 캐리지 리턴(첫 번째로 돌아가기) -> 현재는 개행문자로 인식
		// \n : new line(다음 줄로 이동)
	}
	
	// XSS : 관리자가 아닌 이용자가 악성 스크립트를 삽입해서 공격
	
	// Cross Site Scripting(XSS, 크로스 사이트 스크립팅) 공격 방지 처리 메서드
	public static String XSSHandling(String content) {
		
		// <, >, &, " 문자를 HTML 코드가 아닌 문자 그대로 보이도록 변경

		if(content != null) {
			
			content = content.replaceAll("&", "&amp;");
			content = content.replaceAll("<", "&lt;");
			content = content.replaceAll(">", "&gt;");
			content = content.replaceAll("\"", "&quot;");
		}
		
		return content;
	}
	
}

🔎 ReplyController.java

package edu.kh.community.board.controller;

import java.io.IOException;
import java.util.List;

import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import com.google.gson.Gson;

import edu.kh.community.board.model.service.ReplyService;
import edu.kh.community.board.model.vo.Reply;

// Controller : 요청에 따라 알맞은 서비스를 호출하고
// 				요청 처리 결과를 내보내 줄(응답할) view를 선택

// *** Front Controller 패턴 ***
// 하나의 Servlet이 여러 요청을 받아들이고 제어하는 패턴

@WebServlet("/reply/*") // reply로 시작하는 모든 요청을 받음
public class ReplyController extends HttpServlet {
	
	// /reply/selectReplyList
	// /reply/insert
	// /reply/update
	// /reply/delete
	
	@Override
	protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
		// GET방식 요청 처리
		String uri = req.getRequestURI();
		String contextPath = req.getContextPath();
		String command = uri.substring(  (contextPath + "/reply/").length()  );
					
		ReplyService service = new ReplyService();

		try {
			// 댓글 목록 조회 요청인 경우
			if(command.equals("selectReplyList")) {
				
				// 파라미터를 얻어와 정수 형태로 파싱
				int boardNo = Integer.parseInt(req.getParameter("boardNo"));
				
				// 댓글 목록 조회 서비스 호출 후 결과 반환 받기
				List<Reply> rList = service.selectReplyList(boardNo);
				
				// JSON 변환 + 응답
				new Gson().toJson(rList, resp.getWriter());

			}

			// 댓글 등록
			if(command.equals("insert")) {
				
				// 파라미터 얻어오기
				String replyContent = req.getParameter("replyContent");
				int memberNo = Integer.parseInt(req.getParameter("memberNo"));
				int boardNo = Integer.parseInt(req.getParameter("boardNo"));
				
				// Reply 객체를 생성해서 파라미터 담기
				Reply reply = new Reply();
				reply.setReplyContent(replyContent);
				reply.setMemberNo(memberNo);
				reply.setBoardNo(boardNo);
				
				// 댓글 등록(insert) 서비스 호출 후 결과 반환 받기
				int result = service.insertReply(reply);
				
				// 서비스 호출 결과를 그대로 응답 데이터로 내보내기
				resp.getWriter().print(result);
				
			}

		} catch (Exception e) {
				e.printStackTrace();
		}
	
	}
	
	@Override
	protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
		// POST방식 요청 처리
		doGet(req, resp); // POST로 전달된 요청을 doGet()으로 전달하여 수행
	}

}

🔎 ReplyService.java

	/** 댓글 작성 Service
	 * @param reply
	 * @return result
	 * @throws Exception
	 */
	public int insertReply(Reply reply) throws Exception {
		
		Connection conn = getConnection();

		// Cross Site Scripting(XSS, 크로스 사이트 스크립팅) 공격 
		reply.setReplyContent( Util.XSSHandling( reply.getReplyContent() ));

		
		// - 개행문자 변경 처리
		// textarea에 줄바꿈 문자 입력 시 \n, \r, \r\n, \n\r 중 하나로 입력이 된다(브라우저, OS 따라 다름)
		// 이 문자들을 HTML에서 줄바꿈으로 인식할 수 있도록 "<br>" 태그로 변경
		
		// reply.getReplyContent().replaceAll("정규표현식", "바꿀 문자열");
		
		// 댓글 등록/수정
		// 게시글 등록/수정 에서 사용
		// reply.setReplyContent( reply.getReplyContent().replaceAll("\n|\r|\r\n|\n\r", "<br>") );
		
		// static으로 선언해 둔 개행문자 변경 메소드 사용
		reply.setReplyContent( Util.newLineHandling(reply.getReplyContent() ));

		
		int result = dao.insertReply(conn, reply);
		
		if(result > 0) commit(conn);
		else		   rollback(conn);
		
		close(conn);
		
		return result;
	}

🔎 ReplyDAO.java

	/** 댓글 작성 DAO
	 * @param conn
	 * @param reply
	 * @return result
	 * @throws Exception
	 */
	public int insertReply(Connection conn, Reply reply) throws Exception {
		
		int result = 0;
		
		try {
			String sql = prop.getProperty("insertReply");
			
			pstmt = conn.prepareStatement(sql);
			
			pstmt.setString(1, reply.getReplyContent());
			pstmt.setInt(2, reply.getMemberNo());
			pstmt.setInt(3, reply.getBoardNo());
			
			result = pstmt.executeUpdate();
			
		} finally {
			close(pstmt);
		}
		
		return result;
	}

🔎 reply-sql.xml

	<!-- 댓글 작성 -->
	<entry key="insertReply">
		INSERT INTO REPLY
		VALUES(SEQ_RNO.NEXTVAL, ?, DEFAULT, DEFAULT, ?, ?)
	</entry>

📌 출력 화면

내가 작성한 댓글이 등록되는 모습을 볼 수 있다. 개행까지 완료된 모습!

profile
풀스택 개발자 기록집 📁

1개의 댓글

comment-user-thumbnail
2023년 7월 21일

정말 잘 읽었습니다, 고맙습니다!

답글 달기