
쿠키
if (loginMember == null || loginMember.getMemberNo() != board.getMemberNo()) {
Cookie[] cookies = req.getCookies();
Cookie c = null;
for (Cookie temp : cookies) {
// 쿠키 중에 "readBoardNo" 가 존재할 때
if (temp.getName().equals("readBoardNo")) {
c = temp;
break;
}
}
int result = 0; // 조회수 증가 결과를 저장할 변수
if (c == null) {
// "readBoardNo" 쿠키가 쿠키에 없을 때
// 새 쿠키 생성("readBoardNo", [2000][3000][1000] => [게시글번호])
// [2000] : boardNo
c = new Cookie("readBoardNo", "[" + boardNo + "]");
result = service.updateReadCount(boardNo);
if (result > 0) {
// 성공
} else {
// 실패
}
} else {
// "readBoardNo" 쿠키가 쿠키에 있을 때
// k : v
// readBoardNo : [2][30][400]
// 현재 글을 처음 읽는 경우
if (c.getValue().indexOf("[" + boardNo + "]") == -1) {
// indexOf가 -1이면 없는 경우
// 해당 글 쿠키에 누적 + 서비스 호출
// 즉, 현재 읽고있는 게시글을 처음 읽는 경우
c.setValue(c.getValue() + "[" + boardNo + "]");
// ex) [2][30][400][2000][4000]
result = service.updateReadCount(boardNo);
// 조회수 업데이트
}
}
// 조회수 증가 성공 / 조회 성공 시
if (result > 0) {
// 조회수 성공 및 조회수 가져왔을 때
// 앞서 조회했던 board의 readCount 값을
// 현재 가져온 result로 가져와서 다시 세팅해준다.
board.setReadCount(result);
// 쿠키 적용 경로 설정
// "/" 이하 경로 요청 시 쿠키를 서버로 전달
c.setPath("/");
// 쿠키 수명 지정
// 현재 시간을 얻어오기
LocalDateTime now = LocalDateTime.now();
// 다음날 자정 지정
LocalDateTime nextDayMidnight = now.plusDays(1).withHour(0).withMinute(0).withSecond(0).withNano(0);
// 다음날 자정까지 남은 시간 계산 (초단위)
// Duration은 남은 시간을 계산
long secondsUntilNextDay = Duration.between(now, nextDayMidnight).getSeconds();
// 쿠키 수명 설정
c.setMaxAge((int)secondsUntilNextDay);
// 응답객체(resp)을 이용해서 클라이언트에게 전달
resp.addCookie(c);
}
}
/* ------------------ 쿠키를 이용한 조회수 끝 ------------------ */
path = "board/boardDetail";
// src/main/resources/tempaltes/board/boardDetail.html
// board - 게시글 일반 내용 + imageList + commentList
model.addAttribute("board", board);
// 조회된 이미지 목록(imageList)이 있을 경우
if (!board.getImageList().isEmpty()) {
BoardImg thumbnail = null;
// imageList의 0번 인덱스 == IMG_ORDER가 가장 빠른 순서
// 만약 이미지 목록의 0번째 요소의 IMG_ORDER가 0이면 == 썸네일
if (board.getImageList().get(0).getImgOrder() == 0) {
thumbnail = board.getImageList().get(0);
// board.getImageList() -> List<BoardImg>
}
// thumbnail 변수에는
// - 이미지 목록의 0번째 요소가 썸네일이면 썸네일 이미지의 BoardImg 객체
// - 썸네일이 아니라면 null
model.addAttribute("thumbnail", thumbnail);
// start라는 key에 thumbnail이 null이 아닐 때 1 저장, null이면 0 저장
model.addAttribute("start", thumbnail != null ? 1 : 0);
// 썸네일 있을 때 : start=1
// 썸네일 없을 때(일반이미지만 있거나, 등록된 이미지가 아예 없을 때)
// : start=0
}
}
// 조회수 1 증가
@Override
public int updateReadCount(int boardNo) {
// 1. 조회수 1증가 (UPDATE)
int result = mapper.updateReadCount(boardNo);
// 2. 현재 조회 수 조회
if(result > 0) {
return mapper.selectReadCount(boardNo);
}
// UPDATE에 실패한 경우 -1 반환
return -1;
}
// 1. #boardLike 가 클릭 되었을 때
document.querySelector("#boardLike").addEventListener("click", (e) => {
// 2. 로그인 상태가 아닌 경우 동작 X
if (loginMemberNo == null) {
alert("로그인 후 이용해주세요~");
return;
}
// 3. 준비된 3개의 변수를 JS 객체 형태로 생성 (JSON 변환 예정)
const obj = {
memberNo: loginMemberNo,
boardNo: boardNo,
likeCheck: likeCheck,
};
// 4. 좋아요 INSERT/DELETE 비동기 요청
fetch("/board/like", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(obj), // JSON으로 json 형태로 전달
})
.then((resp) => resp.text())
.then((count) => {
if (count == -1) {
console.log("좋아요 처리 실패...");
return;
}
// 5. likeCheck 값을 0 -> 1, 1 -> 0
// -> 클릭될 때마다 INSERT/DELETE 동작을 번갈아 가면서 할 수 있게끔
likeCheck = likeCheck == 0 ? 1 : 0;
// 6. 하트를 채우기/비우기 바꾸기
// 현재 하트가 눌렸을 때가 e.target
e.target.classList.toggle("fa-regular");
e.target.classList.toggle("fa-solid");
// 7. 게시글 좋아요 수 수정
e.target.nextElementSibling.innerText = count;
});
});
/** 게시글 좋아요 체크/해제 (비동기)
* @param map : boardNo, memberNo, likeCheck가 들어있는 map
* @return
*/
@PostMapping("like")
@ResponseBody // /board/like (POST) 요청 매핑
public int boardLike(@RequestBody Map<String, Integer> map) {
// Board DTO로 받을 수도 있고, Map으로 받을 수 있다.
return service.boardLike(map);
}
/**
* map : boardNo, memberNo, likeCheck likeCheck : 좋아요 여부 1이면 누른 것, 0이면 안누른 것 =>
* 이걸 토대로 insert or delete 여부 결정
*/
@Override
public int boardLike(Map<String, Integer> map) {
int result = 0;
if (map.get("likeCheck") == 1) {
// 1. 좋아요가 체크된 상태된 경우(likeCheck == 1)
// -> BOARD_LIKE 테이블에 DELETE
result = mapper.deleteBoardLike(map);
} else {
// 2. 좋아요가 해제된 상태인 경우(likeCheck == 0)
// -> BOARD_LIKE 테이블에 INSERT
result = mapper.insertBoardLike(map);
}
// 3. INSERT/DELETE 둘 중 성공했다면 해당 게시글의 좋아요 갯수 조회해서 반환
if(result > 0) {
return mapper.selectLikeCount(map.get("boardNo"));
}
return -1;
}
<!-- 좋아요 해제 SQL (DELETE) -->
<delete id="deleteBoardLike">
DELETE FROM "BOARD_LIKE"
WHERE MEMBER_NO = #{memberNo}
AND BOARD_NO = #{boardNo}
</delete>
<!-- 좋아요 체크 SQL (INSERT) -->
<insert id="insertBoardLike">
INSERT INTO "BOARD_LIKE"
VALUES(#{memberNo}, #{boardNo})
</insert>
<!-- 게시글 좋아요 갯수 조회 (SELECT) -->
<select id="selectLikeCount">
SELECT COUNT(*)
FROM "BOARD_LIKE"
WHERE BOARD_NO = #{boardNo}
</select>
쿠키
if (loginMember == null || loginMember.getMemberNo() != board.getMemberNo()) {
Cookie[] cookies = req.getCookies();
Cookie c = null;
for (Cookie temp : cookies) {
// 쿠키 중에 "readBoardNo" 가 존재할 때
if (temp.getName().equals("readBoardNo")) {
c = temp;
break;
}
}
int result = 0; // 조회수 증가 결과를 저장할 변수
if (c == null) {
// "readBoardNo" 쿠키가 쿠키에 없을 때
// 새 쿠키 생성("readBoardNo", [2000][3000][1000] => [게시글번호])
// [2000] : boardNo
c = new Cookie("readBoardNo", "[" + boardNo + "]");
result = service.updateReadCount(boardNo);
if (result > 0) {
// 성공
} else {
// 실패
}
} else {
// "readBoardNo" 쿠키가 쿠키에 있을 때
// k : v
// readBoardNo : [2][30][400]
// 현재 글을 처음 읽는 경우
if (c.getValue().indexOf("[" + boardNo + "]") == -1) {
// indexOf가 -1이면 없는 경우
// 해당 글 쿠키에 누적 + 서비스 호출
// 즉, 현재 읽고있는 게시글을 처음 읽는 경우
c.setValue(c.getValue() + "[" + boardNo + "]");
// ex) [2][30][400][2000][4000]
result = service.updateReadCount(boardNo);
// 조회수 업데이트
}
}
// 조회수 증가 성공 / 조회 성공 시
if (result > 0) {
// 조회수 성공 및 조회수 가져왔을 때
// 앞서 조회했던 board의 readCount 값을
// 현재 가져온 result로 가져와서 다시 세팅해준다.
board.setReadCount(result);
// 쿠키 적용 경로 설정
// "/" 이하 경로 요청 시 쿠키를 서버로 전달
c.setPath("/");
// 쿠키 수명 지정
// 현재 시간을 얻어오기
LocalDateTime now = LocalDateTime.now();
// 다음날 자정 지정
LocalDateTime nextDayMidnight = now.plusDays(1).withHour(0).withMinute(0).withSecond(0).withNano(0);
// 다음날 자정까지 남은 시간 계산 (초단위)
// Duration은 남은 시간을 계산
long secondsUntilNextDay = Duration.between(now, nextDayMidnight).getSeconds();
// 쿠키 수명 설정
c.setMaxAge((int)secondsUntilNextDay);
// 응답객체(resp)을 이용해서 클라이언트에게 전달
resp.addCookie(c);
}
}
/* ------------------ 쿠키를 이용한 조회수 끝 ------------------ */
path = "board/boardDetail";
// src/main/resources/tempaltes/board/boardDetail.html
// board - 게시글 일반 내용 + imageList + commentList
model.addAttribute("board", board);
// 조회된 이미지 목록(imageList)이 있을 경우
if (!board.getImageList().isEmpty()) {
BoardImg thumbnail = null;
// imageList의 0번 인덱스 == IMG_ORDER가 가장 빠른 순서
// 만약 이미지 목록의 0번째 요소의 IMG_ORDER가 0이면 == 썸네일
if (board.getImageList().get(0).getImgOrder() == 0) {
thumbnail = board.getImageList().get(0);
// board.getImageList() -> List<BoardImg>
}
// thumbnail 변수에는
// - 이미지 목록의 0번째 요소가 썸네일이면 썸네일 이미지의 BoardImg 객체
// - 썸네일이 아니라면 null
model.addAttribute("thumbnail", thumbnail);
// start라는 key에 thumbnail이 null이 아닐 때 1 저장, null이면 0 저장
model.addAttribute("start", thumbnail != null ? 1 : 0);
// 썸네일 있을 때 : start=1
// 썸네일 없을 때(일반이미지만 있거나, 등록된 이미지가 아예 없을 때)
// : start=0
}
}
// 조회수 1 증가
@Override
public int updateReadCount(int boardNo) {
// 1. 조회수 1증가 (UPDATE)
int result = mapper.updateReadCount(boardNo);
// 2. 현재 조회 수 조회
if(result > 0) {
return mapper.selectReadCount(boardNo);
}
// UPDATE에 실패한 경우 -1 반환
return -1;
}
// 1. #boardLike 가 클릭 되었을 때
document.querySelector("#boardLike").addEventListener("click", (e) => {
// 2. 로그인 상태가 아닌 경우 동작 X
if (loginMemberNo == null) {
alert("로그인 후 이용해주세요~");
return;
}
// 3. 준비된 3개의 변수를 JS 객체 형태로 생성 (JSON 변환 예정)
const obj = {
memberNo: loginMemberNo,
boardNo: boardNo,
likeCheck: likeCheck,
};
// 4. 좋아요 INSERT/DELETE 비동기 요청
fetch("/board/like", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(obj), // JSON으로 json 형태로 전달
})
.then((resp) => resp.text())
.then((count) => {
if (count == -1) {
console.log("좋아요 처리 실패...");
return;
}
// 5. likeCheck 값을 0 -> 1, 1 -> 0
// -> 클릭될 때마다 INSERT/DELETE 동작을 번갈아 가면서 할 수 있게끔
likeCheck = likeCheck == 0 ? 1 : 0;
// 6. 하트를 채우기/비우기 바꾸기
// 현재 하트가 눌렸을 때가 e.target
e.target.classList.toggle("fa-regular");
e.target.classList.toggle("fa-solid");
// 7. 게시글 좋아요 수 수정
e.target.nextElementSibling.innerText = count;
});
});
/** 게시글 좋아요 체크/해제 (비동기)
* @param map : boardNo, memberNo, likeCheck가 들어있는 map
* @return
*/
@PostMapping("like")
@ResponseBody // /board/like (POST) 요청 매핑
public int boardLike(@RequestBody Map<String, Integer> map) {
// Board DTO로 받을 수도 있고, Map으로 받을 수 있다.
return service.boardLike(map);
}
/**
* map : boardNo, memberNo, likeCheck likeCheck : 좋아요 여부 1이면 누른 것, 0이면 안누른 것 =>
* 이걸 토대로 insert or delete 여부 결정
*/
@Override
public int boardLike(Map<String, Integer> map) {
int result = 0;
if (map.get("likeCheck") == 1) {
// 1. 좋아요가 체크된 상태된 경우(likeCheck == 1)
// -> BOARD_LIKE 테이블에 DELETE
result = mapper.deleteBoardLike(map);
} else {
// 2. 좋아요가 해제된 상태인 경우(likeCheck == 0)
// -> BOARD_LIKE 테이블에 INSERT
result = mapper.insertBoardLike(map);
}
// 3. INSERT/DELETE 둘 중 성공했다면 해당 게시글의 좋아요 갯수 조회해서 반환
if(result > 0) {
return mapper.selectLikeCount(map.get("boardNo"));
}
return -1;
}
<!-- 좋아요 해제 SQL (DELETE) -->
<delete id="deleteBoardLike">
DELETE FROM "BOARD_LIKE"
WHERE MEMBER_NO = #{memberNo}
AND BOARD_NO = #{boardNo}
</delete>
<!-- 좋아요 체크 SQL (INSERT) -->
<insert id="insertBoardLike">
INSERT INTO "BOARD_LIKE"
VALUES(#{memberNo}, #{boardNo})
</insert>
<!-- 게시글 좋아요 갯수 조회 (SELECT) -->
<select id="selectLikeCount">
SELECT COUNT(*)
FROM "BOARD_LIKE"
WHERE BOARD_NO = #{boardNo}
</select>