#231016 수업 내용 복습
REST(Representational State Transfer)
"하나의 URI는 하나의 고유한 리소스를 대표한다"
하나의 주소에 하나의 리소스를 매칭시키며 설계한다.
CRUD를 아래 방식으로 type 지정
POST(생성), GET(읽어오기), DELETE(삭제), PUT(생성/수정), PATCH(수정)
/board/get?boardnum=3
/board/3 - GET
/board/remove?boardnum=3
/board/3 - DELETE
<div class="reply_line">
<a href="#" class="regist">댓글 등록</a>
<div class="replyForm row">
<div style="width:20%">
<h4>작성자</h4>
<input type="text" name="userid" th:value="${session.loginUser}" readonly style="text-align: center;">
</div>
<div style="width:65%">
<h4>내 용</h4>
<textarea name="replycontents" placeholder="Contents" style="resize:none;"></textarea>
</div>
<div style="width:15%">
<a href="#" class="button finish" style="margin-bottom:1rem;">등록</a>
<a href="#" class="button cancel">취소</a>
</div>
</div>
<!-- 댓글 띄워주는 부분 -->
<ul class="replies"></ul>
<!-- 댓글 페이징 -->
<div class="page"></div>
</div>
<!-- 댓글 기능 부분 javascript -->
<script th:inline="javascript">
const loginUser = /*[[${session.loginUser}]]*/'';
const boardnum = /*[[${board.boardnum}]]*/'';
const replies = $(".replies")
const page = $(".page")
let pagenum = 0;
$(document).ready(function(){
$(".replyForm").hide(); //댓글 입력하는 부분은 처음에는 보이지 않게 hide
nowpage = 1;
showList(1);
})
//댓글등록(class=regist)
$(".regist").on("click",function(e){
e.preventDefault(); //댓글등록을 클릭했을 때 페이지이동 없도록 기능 막기(a태그)
$(".replyForm").show(); //hide했던 replyForm 보여주기
$(this).hide(); //클릭했던 '댓글등록' 버튼 hide
})
//등록(class=finish) (작성한 댓글 데이터를 POST방식으로 백에 보내주어야함)
//'등록' 버튼을 누르면 댓글작성폼은 없어지고, '댓글등록' 버튼 다시 보여져야함
$(".finish").on("click",function(e){
e.preventDefault(); //등록을 클릭했을 때 페이지이동 없도록 기능 막기(a태그)
let replycontents = $("[name='replycontents']").val(); //댓글내용 작성값 변수에 담기
//'자바스크립트->자바'로 데이터 전송하기 위해 JSON 형태로 보내줌
//REST방식으로 설계하기 위해 Ajax통신 코드 작성, 결과 돔 구현 코드 작성을 해야함
//Ajax, 돔 분리하여 모듈화
replyService.add(
{"boardnum":boardnum,"userid":loginUser, "replycontents":replycontents},
function(result){
alert("등록!");
showList(1);
}
)
})
//Ajax 통신
/*
function f(){
return {};
}
const replyService = f();
이걸 줄인게 밑에거
*/
const replyService = (function(){
//외부에서 ajax 통신만 함
//댓글 등록
function insert(reply, callback){ //reply 객체와 callback 함수 받아옴(reply : ajax통신할 데이터 / callback함수 : 어떤 돔이 구현되어 있는 함수)
$.ajax({ //어떤식으로 통신할지 정의해서 객체로 넘겨줌
type:"POST", //전송방식
url:"/reply/regist", //url
data:JSON.stringify(reply), //보낼 데이터
contentType:"application/json;charset=utf-8", //보내고 있는 내용의 형태
success:function(result,status,xhr){ //성공시
callback(result);
}
});
}
//댓글목록 띄우는 함수
function selectAll(data,callback){
let boardnum = data.boardnum;
let pagenum = data.pagenum;
$.getJSON(
"/reply/pages/"+boardnum+"/"+pagenum,
function(data){
//data : {replyCnt:댓글개수, list:[....]}
callback(data.replyCnt, data.list);
}
)
}
//댓글 삭제
function drop(replynum,callback){
$.ajax({
type:"DELETE",
url:"/reply/"+replynum,
success:function(result,status,xhr){
callback(result);
},
error:function(xhr,status,err){
error(err);
}
})
}
//댓글 수정
function update(reply,callback){
$.ajax({
type:"PUT",
url:"/reply/"+reply.replynum,
data:JSON.stringify(reply),
contentType:"application/json;charset=utf-8",
success:function(result){
if(callback){
callback(result);
}
},
error:function(err){
if(error){
error(err);
}
}
})
}
//포매팅 타임(시간 포매팅)
function fmtTime(reply){
const regdate = reply.regdate;
const updatedate = reply.updatedate;
const now = new Date();
const check = regdate == updatedate;
const dateObj = new Date(check ? regdate : updatedate);
//date객체.getTime() : date객체가 가지고 있는 시간 정보를 밀리초 단위로 추출
let gap = now.getTime() - dateObj.getTime();
let str = "";
if(gap < 1000*60*60*24){
let hh = dateObj.getHours();
let mi = dateObj.getMinutes();
let ss = dateObj.getSeconds();
str = (hh>9?'':'0')+hh+":"+(mi>9?'':'0')+mi+":"+(ss>9?'':'0')+ss;
}
else{
let yy = dateObj.getFullYear();
let mm = dateObj.getMonth()+1;
let dd = dateObj.getDate();
str = (yy>9?'':'0')+yy+"/"+(mm>9?'':'0')+mm+":"+(dd>9?'':'0')+dd;
}
return (check?'':'(수정됨) ')+str;
}
return {add:insert, getList:selectAll, remove:drop, modify:update, displayTime:fmtTime}
})(); //함수 만듦과 동시에 호출
<script th:src="@{/board/js/reply.js}"></script>로 js 연결 replyService.add(
<!-- 첫번째 매개변수 : 객체 -->
{"boardnum":boardnum,"userid":loginUser, "replycontents":replycontents},
<!-- 두번째 매개변수 : 함수 -->
function(result){
alert("등록!");
}
)
const replyService = (function(){
//insert : 외부에서 ajax 통신만 함
function insert(reply, callback){ //reply 객체와 callback 함수 받아옴(reply : ajax통신할 데이터 / callback함수 : 어떤 돔이 구현되어 있는 함수)
$.ajax({ //어떤식으로 통신할지 정의해서 객체로 넘겨줌
type:"POST", //전송방식
url:"/reply/regist", //url
data:JSON.stringify(reply), //보낼 데이터
contentType:"application/json;charset=utf-8", //보내고 있는 내용의 형태
success:function(result,status,xhr){ //성공시
callback(result);
}
});
}
return {add:insert}
package com.kh.demo.controller;
@RequestMapping("/reply/*")
@RestController
public class ReplyController {
@PostMapping(value = "regist", consumes = "application/json") //consumes : 소비할 데이터
public ResponseEntity<String> regist(){
}
}
package com.kh.demo.mapper;
import java.util.List;
import org.apache.ibatis.annotations.Mapper;
import com.kh.demo.domain.dto.Criteria;
import com.kh.demo.domain.dto.ReplyDTO;
@Mapper
public interface ReplyMapper {
//insert
int insertReply(ReplyDTO reply);
//update
int updateReply(ReplyDTO reply);
//delete
int deleteReply(Long replynum);
int deleteByBoardnum(Long boardnum);
//select
Long getLastNum(String userid);
int getTotal(Long boardnum);
List<ReplyDTO> getList(Criteria cri, Long boardnum);
int getRecentReply(Long boardnum);
}
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.kh.demo.mapper.ReplyMapper">
<insert id="insertReply">
insert into t_reply(replycontents, userid, boardnum)
values(#{replycontents},#{userid},#{boardnum})
</insert>
<update id="updateReply">
update t_reply set replycontents=#{replycontents}, updatedate=now()
where replynum=#{replynum}
</update>
<delete id="deleteReply">
delete from t_reply where replynum=#{replynum}
</delete>
<delete id="deleteByBoardnum">
delete from t_reply where boardnum=#{boardnum}
</delete>
<select id="getTotal">
select count(*) from t_reply where boardnum=#{boardnum}
</select>
<select id="getRecentReply">
<![CDATA[
select count(*) from t_reply where boardnum=#{boardnum} and timestampdiff(HOUR,regdate,now())<1
]]>
</select>
<select id="getLastNum">
select max(replynum) from t_reply where userid=#{userid}
</select>
<select id="getList">
select * from t_reply where boardnum=#{boardnum}
limit #{cri.startrow},#{cri.amount}
</select>
</mapper>
package com.kh.demo.service;
import com.kh.demo.domain.dto.Criteria;
import com.kh.demo.domain.dto.ReplyDTO;
import com.kh.demo.domain.dto.ReplyPageDTO;
public interface ReplyService {
boolean regist(ReplyDTO reply);
boolean modify(ReplyDTO reply);
boolean remove(Long replynum);
ReplyPageDTO getList(Criteria cri, Long boardnum);
Long getLastNum(String userid);
}
package com.kh.demo.service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.kh.demo.domain.dto.Criteria;
import com.kh.demo.domain.dto.ReplyDTO;
import com.kh.demo.domain.dto.ReplyPageDTO;
import com.kh.demo.mapper.ReplyMapper;
@Service
public class ReplyServiceImpl implements ReplyService {
@Autowired
private ReplyMapper rmapper;
@Override
public boolean regist(ReplyDTO reply) {
return rmapper.insertReply(reply) == 1;
}
@Override
public boolean modify(ReplyDTO reply) {
return rmapper.updateReply(reply) == 1;
}
@Override
public boolean remove(Long replynum) {
return rmapper.deleteReply(replynum) == 1;
}
@Override
public ReplyPageDTO getList(Criteria cri, Long boardnum) {
return new ReplyPageDTO(rmapper.getTotal(boardnum), rmapper.getList(cri, boardnum));
}
@Override
public Long getLastNum(String userid) {
return rmapper.getLastNum(userid);
}
}
package com.kh.demo.domain.dto;
import java.util.List;
import lombok.AllArgsConstructor;
import lombok.Data;
@Data
@AllArgsConstructor
public class ReplyPageDTO {
int replyCnt; //페이징처리때매 댓글 전체 갯수 필요
List<ReplyDTO> list;
}
package com.kh.demo.controller;
//import 생략
@RequestMapping("/reply/*")
@RestController
public class ReplyController {
@Autowired
private ReplyService service;
//ResponseEntity : 서버의 상태코드, 응답 메세지, 응답 데이터 등을 담을 수 있는 타입
//consumes : 이 메소드가 호출될 때 소비할 데이터의 타입(넘겨지는 RequestBody의 데이터 타입)
//@RequestBody : 넘겨지는 body의 데이터 타입을 해석해서 해당 파라미터에 채워넣기
@PostMapping(value = "regist", consumes = "application/json") //consumes : 소비할 데이터
public ResponseEntity<String> regist(@RequestBody ReplyDTO reply){
boolean check = service.regist(reply);
Long replynum = service.getLastNum(reply.getUserid());
return check ? new ResponseEntity<String>(replynum+"",HttpStatus.OK) :
new ResponseEntity<String>(HttpStatus.INTERNAL_SERVER_ERROR);
//성공하면 OK, 실패하면 error
}
// /reply/pages/100/1 ; 100번 게시글의 1 페이지 댓글 리스트
@GetMapping(value = "/pages/{boardnum}/{pagenum}")
public ResponseEntity<ReplyPageDTO> getList(
//@PathVariable : path에 포함된 변수
@PathVariable("boardnum") Long boardnum, //path에 있는 boardnum을 boardnum 변수에 담기
@PathVariable("pagenum") int pagenum
){
Criteria cri = new Criteria(pagenum, 5); //amount=5 -> 한페이지에 댓글 5개씩
return new ResponseEntity<ReplyPageDTO>(service.getList(cri, boardnum), HttpStatus.OK);
}
//@DeleteMapping : REST 방식의 설계 이용 시 삭제 요청을 받을 때 사용하는 매핑 방식
//produces : 이 메소드가 호출된 결과로 생산해낼 데이터의 타입(돌려주는 ResponseBody의 데이터 타입)
@DeleteMapping(value = "{replynum}", produces = MediaType.TEXT_PLAIN_VALUE)
public ResponseEntity<String> remove(@PathVariable("replynum") Long replynum){
return service.remove(replynum) ?
new ResponseEntity<String>("success",HttpStatus.OK) :
new ResponseEntity<String>(HttpStatus.INTERNAL_SERVER_ERROR);
}
//수정 => PUT, PATCH
//PUT
// 모든 데이터들을 다 전달, 자원의 전체 수정, 자원 내의 모든 필드를 전달해야 함
//PATCH
// 자원의 일부 수정, 수정할 필드만 전송
//@PatchMapping(value = "{replynum}", consumes = "application/json")
@PutMapping(value = "{replynum}", consumes = "application/json")
public ResponseEntity<String> modify(@RequestBody ReplyDTO reply){
return service.modify(reply) ?
new ResponseEntity<String>("success", HttpStatus.OK) :
new ResponseEntity<String>(HttpStatus.INTERNAL_SERVER_ERROR);
}
}
<!-- 댓글 기능 돔 구현 부분 javascript -->
<script th:inline="javascript">
//전역변수 선언
const replies = $(".replies")
const page = $(".page")
let nowpage = 0;
function showList(pagenum){
replyService.getList(
{boardnum:boardnum, pagenum:pagenum||1}, //pagenum이 비어있다면 1
function(replyCnt, list){
let str = "";
if(list == null || list.length == 0){
str+= '<li class="noreply" style="clear:both;">등록된 댓글이 없습니다.</li>';
replies.html(str);
return;
}
for(let i=0;i<list.length;i++){
//<li style="clear:both;" class="li3">
str += '<li style="clear:both;" class="li'+list[i].replynum+'">';
str += '<div style="display:inline; float:left;9
width:80%;">';
//<strong class="userid3">apple</strong>
str += '<strong class="userid'+list[i].replynum+'">'+list[i].userid+'</strong>';
//<p class="reply3">댓글내용</p>
str += '<p class="reply'+list[i].replynum+'">'+list[i].replycontents+'</p>';
str += '</div><div style="text-align:right;">'
str += '<strong>'+replyService.displayTime(list[i])+'</strong>'
if(list[i].userid == loginUser){
//<a href="3" class="modify">수정</a>
str += '<a href="'+list[i].replynum+'" class="modify">수정</a>';
str += '<a href="'+list[i].replynum+'" class="mfinish" style="display:none;">수정 완료</a>';
str += '<a href="'+list[i].replynum+'" class="remove">삭제</a>';
}
str += '</div></li>';
}
replies.html(str);
$(".remove").on("click",deleteReply);
$(".modify").on("click",modifyReply);
$(".mfinish").on("click",modifyReplyOk);
showReplyPage(replyCnt, pagenum);
}
)
}
function showReplyPage(replyCnt, pagenum){
let endPage = Math.ceil(pagenum/5)*5;
let startPage = endPage - 4;
let prev = startPage != 1;
endPage = (endPage-1)*5 >= replyCnt ? Math.ceil(replyCnt/5) : endPage;
let next = endPage*5 < replyCnt ? true : false;
let str = "";
if(prev){
//<a class="changePage" href="5"><code><</code></a>
str += '<a class="changePage" href="'+(startPage-1)+'"><code><</code></a>';
}
for(let i=startPage;i<=endPage;i++){
if(i == pagenum){
//<code class="nowPage">7</code>
str += '<code class="nowPage">'+i+'</code>';
}
else{
//<a class="changePage" href="9"><code>9</code></a>
str += '<a class="changePage" href="'+i+'"><code>'+i+'</code></a>';
}
}
if(next){
str += '<a class="changePage" href="'+(endPage+1)+'"><code>></code></a>';
}
page.html(str);
$(".changePage").on("click",function(e){
e.preventDefault();
let target = $(this).attr("href");
nowpage = parseInt(target);
showList(nowpage);
window.setTimeout(function(){
window.scrollTo(0,document.body.scrollHeight)
},10)
})
}
function deleteReply(e){
e.preventDefault(); //페이지 이동 막아주기
let replynum = $(this).attr("href");
replyService.remove(
replynum,
function(result){
if(result == "success"){
alert(replynum+"번 댓글 삭제 완료!");
showList(nowpage);
}
}
)
}
let replyFlag = false;
function modifyReply(e){
e.preventDefault();
if(!replyFlag){ //여러개 한번에 수정 안되게 하기 위한 flag
replyFlag = true;
let replynum = $(this).attr("href");
const replyTag = $(".reply"+replynum);
//<textarea class="3 mdf">댓글내용</textarea>
replyTag.html('<textarea class="'+replynum+' mdf">'+replyTag.text()+'</textarea>')
$(this).hide();
$(this).next().show();
}
else{
alert("수정중인 댓글이 있습니다.");
}
}
function modifyReplyOk(e){
e.preventDefault();
replyFlag = false;
let replynum = $(this).attr("href");
let replycontents = $("."+replynum).val();
if(replycontents == ""){
alert("수정할 댓글 내용을 입력하세요.");
return;
}
replyService.modify(
{replynum:replynum, replycontents:replycontents, boardnum:boardnum, userid:loginUser},
function(result){
if(result == "success"){
alert(replynum+"번 댓글 수정 완료!");
showList(nowpage);
}
}
)
}
function modify(){
const boardForm = document.boardForm;
boardForm.setAttribute("action",/*[[@{/board/modify}]]*/'');
boardForm.setAttribute("method","get");
boardForm.submit();
}
$(".cancel").on("click",function(e){
e.preventDefault();
$(".replyForm").hide();
$(".regist").show();
$("[name='replycontents']").val("");
})
</script>