어제 댓글 목록 조회할 때 만들어 놓은 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);
}
});
})
- 개행 문자 -> br 변경 메서드 (줄바꿈)
- 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("&", "&");
content = content.replaceAll("<", "<");
content = content.replaceAll(">", ">");
content = content.replaceAll("\"", """);
}
return content;
}
}
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()으로 전달하여 수행
}
}
/** 댓글 작성 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;
}
/** 댓글 작성 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;
}
<!-- 댓글 작성 -->
<entry key="insertReply">
INSERT INTO REPLY
VALUES(SEQ_RNO.NEXTVAL, ?, DEFAULT, DEFAULT, ?, ?)
</entry>
내가 작성한 댓글이 등록되는 모습을 볼 수 있다. 개행까지 완료된 모습!
정말 잘 읽었습니다, 고맙습니다!