리뷰 게시판의 댓글 기능을 구현해 보았다.
replyno | number | pk | 댓글 pk |
reviewno | number | fk | 댓글 단 게시글 번호 |
reply | varchar2 | nn | 댓글 내용 |
replyer | varchar2 | fk | 댓글을 단 사용자 |
nth | number | default 0 | 댓글 레벨(댓글=0, 리댓글=1) |
reReplyno | number | nn | 리댓글 단 댓글 번호 (nth =0 인 경우 그냥 본인의 pk가 들어가도록 함.) |
reSum | number | default 0 | 달린 리댓글 개수 (댓글에만 들어감) |
replyDate | date | default sysdate | 작성일 |
1. 게시글마다 댓글(+리댓)을 뿌려준다.
1-1. 처음에는 1페이지의 댓글을 뿌려줌
1-2. 새로 댓글을 작성하면 마지막 페이지로 이동한다.
2. 댓글(nth = 0)에 리댓(nth = 1)을 달 수 있다.
2-1.리댓에는 리리댓을 달 수 없다.
3. 작성한 댓글은 작성자 본인만 삭제할 수 있다.(작성자에게만 버튼이 보임)
3-1. 리댓이 달려있는 댓글은 삭제할 수 없다.
1. 게시글마다 댓글을 뿌려준다.
1-1. 처음에는 1페이지의 댓글을 뿌려줌
1-2. 새로 댓글을 작성하면 마지막 페이지로 이동한다.
//댓글목록 가져오기
function showList(page){
console.log("show list "+page);
//page(파라미터값)가 없을 때는 1로 설정
replyService.getList({reviewno:reviewnoValue,page: page|| 1 },
function(replyCnt, list){
console.log("replyCnt: " +replyCnt);
console.log(list)
//만약에 page번호가 -1이면
if(page == -1){
//(찐)마지막페이지로 감 =>1-2
pageNum = Math.ceil(replyCnt/10.0);
showList(pageNum);
return;
}
var str="";
//가져올 댓글이 없으면 return
if(list == null || list.length == 0){
return;
}
//댓글이 있으면 있는 만큼 반복해서 가져옴
for(var i =0, len =list.length || 0; i<len; i++){
str +="<ul class='chat'><li class='clearfix' data-replyno='"+list[i].replyno+"'>";
str += list[i].replyer+"</li>";
str +=" <li class='reply-content'>"+list[i].reply;
//작성자 또는 관리자만 삭제할 수 있도록 설정
var id;
var admin;
<sec:authorize access="isAuthenticated()">
id="<sec:authentication property='principal.username' />";
</sec:authorize>
<sec:authorize access="hasRole('ROLE_ADMIN')">
admin="ture";
</sec:authorize>
//삭제버튼
if((id == list[i].replyer) || admin){
str +="<img id='modalRemoveBtn' data-rrr='"+list[i].reReplyno+"' data-re='"+list[i].reSum+"' data-replyer='"+list[i].replyer+"' data-replyno='"+list[i].replyno+"' src='/resources/image/delete.png'>";
}
str +=" <li class='pull-right text-muted'>"
+replyService.displayTime(list[i].replyDate)+"</li></ul><hr style='margin-top:7px'>";
}
replyUL.html(str); //태그에 넣어줌
showReplyPage(replyCnt); //페이지 출력하는 함수
});//end function
}//end showList
+페이지 출력 함수
var pageNum = 1;
var replyPageFooter = $(".panel-footer");
//댓글 페이지 번호 출력
function showReplyPage(replyCnt){
//현재 페이지가 13일때 1.3을 올림하면 2로 해서 20이 마지막 페이지가 됨.
var endNum = Math.ceil(pageNum / 10.0) * 10;
var startNum = endNum - 9;
var prev = startNum != 1;
var next = false; //next버튼 막아둠
//마지막 페이지(20)를 10곱한 값(200)이 총 댓글수보다 크면
//빈 페이지가 있는 상태이므로
if(endNum * 10 >= replyCnt){
//총 댓글수를 10으로 나눠서 올림한 값을 맨끝 페이지로 정함.(그리고 next 못누름)
endNum = Math.ceil(replyCnt/10.0);
}
//총 댓글수보다 작으면 next버튼 사용 가넝한.
if(endNum * 10 < replyCnt){
next = true;
}
var str = "<ul class='pagination pull-right'>";
//이전 버튼이 true면 시작번호 -1해줌(1~10, 11~20 이 숫자 조절해주는거)
if(prev){
str+= "<li class='page-item'><a class='page-link' href='"+(startNum -1)+"'>Previous</a></li>";
}
//전체 페이지수 만큼 반복
for(var i = startNum; i<=endNum; i++){
//현재 페이지가 i이면 css클래스 붙여주자
var active = pageNum == i? "bold":"";
str+= "<li class='page-item "+active+" '><a class='page-link' href='"+i+"'>"+i+" </a></li>";
}
//next가 true면 끝번호 +1해줌(1~10, 11~20 이 숫자 조절해주는거)
if(next){
str+= "<li class='page-item'><a class='page-link' href='"+(endNum + 1)+"'>Next</a></li>";
}
str += "</ul></div>";
console.log(str);
replyPageFooter.html(str); // 태그안에 페이지 붙여줌
}
//페이지 번호를 클릭했을 때 새로운 댓글을 가져옴
replyPageFooter.on("click","li a", function(e){
//a태그 기능 제거하고
e.preventDefault();
console.log("page click");
//클릭한 값을 담아서
var targetPageNum = $(this).attr("href");
console.log("targetPageNum: "+ targetPageNum);
//현재 페이지에 넣어줌
pageNum = targetPageNum;
//해당 페이지목록을 띄움
showList(pageNum);
})
2. 댓글(nth = 0)에 리댓(nth = 1)을 달 수 있다.
2-1.리댓에는 리리댓을 달 수 없다.
//댓글목록 가져오기(리댓 추가)
function showList(page){
console.log("show list "+page);
//page(파라미터값)가 없을 때는 1로 설정
replyService.getList({reviewno:reviewnoValue,page: page|| 1 },
function(replyCnt, list){
console.log("replyCnt: " +replyCnt);
console.log(list)
//만약에 page번호가 -1이면
if(page == -1){
//(찐)마지막페이지로 감 =>1-2
pageNum = Math.ceil(replyCnt/10.0);
showList(pageNum);
return;
}
var str="";
if(list == null || list.length == 0){
//replyUL.html("");
return;
}
for(var i =0, len =list.length || 0; i<len; i++){
//nth가 1이면 태그 추가(리댓 들어갈 곳)
if(list[i].nth){
str += "<span class='rechat'>";
}
str +="<ul class='chat'><li class='clearfix' data-replyno='"+list[i].replyno+"'>";
//nth가 1이면 리댓 표시 추가(└)
if(list[i].nth){
str += "<span>└</span>";
}
str += list[i].replyer+"</li>";
str +=" <li class='reply-content'>"+list[i].reply;
//작성자 또는 관리자만 삭제할 수 있도록 설정
var id;
var admin;
<sec:authorize access="isAuthenticated()">
id="<sec:authentication property='principal.username' />";
</sec:authorize>
<sec:authorize access="hasRole('ROLE_ADMIN')">
admin="ture";
</sec:authorize>
//삭제버튼
if((id == list[i].replyer) || admin){
str +="<img id='modalRemoveBtn' data-rrr='"+list[i].reReplyno+"' data-re='"+list[i].reSum+"' data-replyer='"+list[i].replyer+"' data-replyno='"+list[i].replyno+"' src='/resources/image/delete.png'>";
}
//리댓버튼(nth가 0일때만)
if(id && !list[i].nth){
str +="<img id='modalRereplyBtn' data-nth='"+list[i].nth+"' data-replyno='"+list[i].replyno+"' data-replyer='"+list[i].replyer+"' data-replyno='"+list[i].replyno+"' src='/resources/image/rereply.png'></li>";
}
str +=" <li class='pull-right text-muted'>"
+replyService.displayTime(list[i].replyDate)+"</li></ul><hr style='margin-top:7px'>";
//nth가 1이면 태그 닫아줌
if(list[i].nth){
str += "</span>";
}
}
replyUL.html(str);
showReplyPage(replyCnt);//페이지 출력하는 함수 시행
});//end function
}//end showList
댓글 추가
//리댓버튼 눌렀으면 input[name='reReplynum']에 누른 댓글의 번호를 담는다
$('.panel-body').on("click", "#modalRereplyBtn", function(e){
e.preventDefault();
modal.find("input[name='reReplynum']").val($(this).data("replyno"));
//입력 창에 누른 댓글의 아이디를 표시해 주었음
$("#replyContent").attr("placeholder", "@"+$(this).data("replyer"));
});
//댓글 추가
modalRegisterBtn.on("click",function(e){
e.preventDefault();
//리댓버튼 안눌렀으면(input[name='reReplynum']에 아무것도 안담겼으면)
if(!reReplynum.val()){
//댓글 정보 담아서
var reply = {
reply : inputReply.val(),
replyer: inputReplyer.val(),
reviewno: reviewnoValue,
nth:"0"
};
//기본 댓글로 insert해줌
replyService.add(reply, function(result){
modal.find("textarea").val("");
showList(-1);
});
//리댓 눌렀으면
}else{
//댓글 정보 담아서
var reply = {
reply : inputReply.val(),
replyer: inputReplyer.val(),
reviewno: reviewnoValue,
nth:"1",
reReplyno: reReplynum.val()
};
//리댓으로 insert해줌(reSum도 +1해주고 있음)
replyService.add(reply, function(result){
//insert성공했으면 댓글창이랑 reReplynum 비워줌
modal.find("textarea").val("");
modal.find("input[name='reReplynum']").val("");
$("#replyContent").attr("placeholder", "댓글을 입력해 주세요.");
showList(pageNum);
});
}
});
3. 작성한 댓글은 작성자 본인만 삭제할 수 있다.
3-1. 리댓이 달려있는 댓글은 삭제할 수 없다.
//삭제하기 버튼 눌렀으면
$('.panel-body').on("click", "#modalRemoveBtn", function(e){
var replyer = $(this).data("replyer");
var replyno = $(this).data("replyno");
var reReplyno = $(this).data("rrr");
var reSum = $(this).data("re");
console.log(replyer, replyno, reSum, reReplyno)
//리댓이 있을 때는 삭제 불가
if(reSum != 0){
alert("답댓글이 달린 댓글은 삭제할 수 없습니다.");
}else if(confirm('선택한 댓글을 삭제 하시겠습니까?')){
//삭제하고
replyService.remove(replyno, function(result){} )
var reply = {
reReplyno: reReplyno
};
//reSum-1해줌
replyService.removeDown(reply, function(result){showList(pageNum)} )
}else
return false;
});
+연결해준 js
var replyService = (function(){
//댓글 등록
function add(reply, callback, error) {
$.ajax({
type : 'post', //전송 방식
url : '/replies/new', //전송 페이지(action)
data : JSON.stringify(reply), //넘길 파라미터(전송할 데이터)//reply를 JSON 문자열로 변환
contentType : "application/json; charset=utf-8", //보내는 데이터의 타입
success : function(result, status, xhr) { //전송 성공시 실행할 코드
if (callback) { //만약에 콜백함수가 값을 주면
callback(result); //실행해!
}
},
error : function(xhr, status, er) { //실패시 실행할 코드
if (error) {
error(er);
}
}
})
}
//리댓글 등록
function addRe(reply, callback, error) {
$.ajax({
type : 'post', //전송 방식
url : '/replies/new_re', //전송 페이지(action)
data : JSON.stringify(reply), //넘길 파라미터(전송할 데이터)//reply를 JSON 문자열로 변환
contentType : "application/json; charset=utf-8", //보내는 데이터의 타입
success : function(result, status, xhr) { //전송 성공시 실행할 코드
if (callback) { //만약에 콜백함수가 값을 주면
callback(result); //실행해!
}
},
error : function(xhr, status, er) { //실패시 실행할 코드
if (error) {
error(er);
}
}
})
}
//댓글 목록
function getList(param, callback, error){
var reviewno = param.reviewno;
var page = param.page || 1;
//원하는 해당자료의 json데이터를 얻기위하여 호출
$.getJSON("/replies/pages/"+reviewno+"/"+page+".json", //[1]url
function(data){ //[2]data
if(callback){ //값이 오면
//callback(data); //댓글 목록만 가져오는 경우
callback(data.replyCnt, data.list); //댓글 숫자와 목록을 가져오는 경우
}
}).fail(function(xhr, status, err){ //[3]success
if(error){
error();
}
});
}
//삭제
function remove(replyno, callback, error){
$.ajax({
type : 'delete',
url : '/replies/'+replyno,
success : function(deleteResult, status, xhr){
if(callback){
callback(deleteResult);
}
},
error : function(xhr, status, er){
if(error){
error(er);
}
}
});
}
function removeDown(reply, callback, error){
$.ajax({
type : 'put',
url : '/replies/'+ reply.reReplyno,
data : JSON.stringify(reply),
contentType : "application/json; charest=utf-8",
success : function(result, status, xhr){
if(callback){
callback(result);
}
},
error : function(xhr, status, er){
if(error){
error(er);
}
}
});
}
//시간 표기
function displayTime(timeValue){
//현재 시간(날짜)
var today = new Date();
//현재 시간과 작성 시간의 차이
var gap = today.getTime() - timeValue;
//작성 시간
var dateObj = new Date(timeValue);
var str="";
//하루 차이보다 적으면 작성 시간 입력(시.분.초)
if( gap < (1000*60*60*24)){
var hh = dateObj.getHours();
var mi = dateObj.getMinutes();
var ss = dateObj.getSeconds();
return [ (hh > 9 ? '' : '0') + hh,':', (mi > 9 ? '' : '0') + mi,
':', (ss > 9 ? '' : '0') + ss ].join('');
//하루가 넘으면 작성 날짜 입력(연.월.일)
}else{
var yy = dateObj.getFullYear();
var mm = dateObj.getMonth() + 1;
var dd = dateObj.getDate();
return [ yy, '/', (mm > 9 ? '' : '0') + mm, '/',
(dd > 9 ? '' : '0') + dd ].join('');
}
};
return {
add : add,
getList : getList,
remove : remove,
removeDown : removeDown,
displayTime : displayTime
};
})();
+Controller
@RequestMapping("/replies/")
@RestController
@Log4j
@AllArgsConstructor
public class ReplyController {
private ReplyService service;
// 들어오는 데이터 타입 정의 //반환하는 데이터 타입 정의 =>JSON방식으로만 처리하도록 함
@PostMapping(value = "/new", consumes = "application/json", produces = { MediaType.TEXT_PLAIN_VALUE })
// JSON데이터를 ReplyVO타입으로 변환하도록 함(JSON->자바객체)
public ResponseEntity<String> create(@RequestBody ReplyVO vo) {
log.info("ReplyVO: " + vo);
if(vo.getNth()==1) {
log.info("ReplyVO: " + vo+ vo.getReReplyno());
int insertCount = service.register(vo, vo.getReReplyno());
log.info("Reply INSERT COUNT: " + insertCount);
//insert성공 했으면 성공 뿌려주고, 아니면 에러 뿌려줘
return insertCount == 1 ? new ResponseEntity<>("success", HttpStatus.OK)
: new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
}else {
int insertCount = service.registerKey(vo);
log.info("Reply INSERT COUNT: " + insertCount);
//insert성공 했으면 성공 뿌려주고, 아니면 에러 뿌려줘
return insertCount == 1 ? new ResponseEntity<>("success", HttpStatus.OK)
: new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
}
}
//목록
@GetMapping(value = "/pages/{reviewno}/{page}",
produces = {MediaType.APPLICATION_XML_VALUE, MediaType.APPLICATION_JSON_VALUE})
public ResponseEntity<ReplyPageDTO> getList(@PathVariable("page") int page, @PathVariable("reviewno") Long reviewno){
Criteria cri = new Criteria(page, 10);
log.info("get Reply List reviewno: "+reviewno);
log.info("cri:"+cri);
return new ResponseEntity<>(service.getListPage(cri, reviewno),HttpStatus.OK);
}
//삭제
@DeleteMapping(value = "/{replyno}", produces = {MediaType.TEXT_PLAIN_VALUE}) //replyno값으로 데이터를 삭제
public ResponseEntity<String> remove(@PathVariable("replyno") Long replyno){
log.info("remove: "+replyno);
return service.remove(replyno) == 1 ? new ResponseEntity<>("success", HttpStatus.OK)
: new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
}
}
'Git · 코드 기록' 카테고리의 다른 글
Git 설치 버전 릴리스 스태시 (모두의 깃&깃허브_강민철 지음) (0) | 2022.11.13 |
---|---|
Spring 게시판 좋아요 기능 구현 (0) | 2022.11.12 |
파일 업로드 처리 (0) | 2022.11.03 |
Spring 페이징처리하기 (0) | 2022.10.27 |
제품 추천 기능 구현하기 (0) | 2022.10.23 |