누가(로그인한 회원 번호) 어떤 게시글(현재 게시글 번호) 좋아요를 클릭/취소
로그인한 회원 번호 얻어오기
1) ajax로 session에 있는 loginMember의 memberNo를 반환
2) HTML 요소에 로그인한 회원의 번호를 숨겨놓고 JS로 얻어오기
3) jsp 파일 제일 위에 있는 scrpit 태그에 JS + EL 이용해서 전역 변수로 선언해두기
<script>
// JSP에서 작성 가능한 언어/라이브러리
// -> html, css, js, java, EL, JSTL
// JSP 해석 우선 순위 : Java/EL/JSTL > HTML,CSS,JS
// 게시글 번호 전역 변수로 선언
const boardNo = "${board.boardNo}";
// 로그인한 회원 번호를 전역 변수로 선언
// -> 작성한 EL 구문이 null일 경우 빈칸으로 출력되어
// 변수에 값이 대입되지 않는 문제가 발생할 수 있음!
// 해결방법 : EL 구문을 '', "" 문자열로 감싸면 해결
// -> EL은 값이 null이여도 ""(빈문자열)로 출력
const loginMemberNo = "${loginMember.memberNo}";
console.log(boardNo);
console.log(loginMemberNo);
</script>
// 좋아요 버튼이 클릭 되었을 때
const boardLike = document.getElementById("boardLike");
// 로그인 여부 검사
boardLike.addEventListener("click", e => {
if(loginMemberNo == ""){
alert("로그인 후 이용해주세요.");
return;
}
let check; // 기존에 좋아요 X(빈 하트) : 0
// 기존에 좋아요 O(꽉찬하트) : 1
// contains("클래스명") : 클래스가 있으면 true, 없으면 false
if(e.target.classList.contains("fa-regular")){ // 좋아요 X
check = 0;
}else{ // 좋아요0 (꽉찬하트)
check = 1;
}
// ajax로 서버로 제출할 파라미터를 모아둔 JS 객체
const data = { "boardNo" : boardNo,
"memberNo" : loginMemberNo,
"check" : check };
// 로그인한 회원 번호, 게시글 번호, 체크
// ajax
fetch("/board/like", {
method : "POST",
headers : {"Content-Type" : "application/json"},
body : JSON.stringify(data)
})
.then(response => response.text()) // 응답 객체를 필요한 형태로 파싱하여 리턴
.then(count => {
console.log("count : " + count);
if(count == -1){ // INSERT, DELETE 실패 시
console.log("좋아요 처리 실패");
return;
}
// toggle() : 클래스가 있으면 없애고, 없으면 추가하고!
e.target.classList.toggle("fa-regular");
e.target.classList.toggle("fa-solid");
// 현재 게시글의 좋아요 수를 화면에 출력
e.target.nextElementSibling.innerText = count;
}) // 파싱된 데이터를 받아서 처리하는 코드 작성
.catch(err => {
console.log("예외 발생");
console.log(err);
}) // 예외 발생 시 처리하는 부분
});
이제 요청 주소로 Mapping을 해야는데 문제점이 하나 있다!!!
@PathVariable 사용 시 문제점과 해결 방법
문제점 : 요청 주소와 @PathVariable로 가져다 쓸 주소의 레벨이 같다면
구분하지 않고 모두 매핑되는 문제가 발생한다
-> 따라서 요청을 했는데 원하는 메소드가 실행 안됨
해결 방법 : @PathVariable 지정 시 정규 표현식 사용해야 함
{키 : 정규표현식}
그래서 어제 적은 코드에 정규 표현식을 추가!
@GetMapping("/{boardCode:[0-9]+}"
그리고 포스트매핑으로 다시 작성해주었ㄷ ㅏㅎㅎ
// 게시글 상세 조회
@GetMapping("/{boardCode}/{boardNo}")
public String boardDetail(@PathVariable("boardCode") int boardCode
, @PathVariable("boardNo") int boardNo
, Model model // 데이터 전달용 객체
, RedirectAttributes ra // 리다이렉트 시 데이터 전달용 객체
, @SessionAttribute(value = "loginMember", required = false) Member loginMember
// 세션에서 loginMember를 얻어오는데 없으면 null, 있으면 회원 정보 저장
// 쿠키를 이용한 조회 수 증가에서 사용
, HttpServletRequest req, HttpServletResponse resp) throws ParseException {
Map<String, Object> map = new HashMap<String, Object>();
map.put("boardCode", boardCode);
map.put("boardNo", boardNo);
// 게시글 상세 조회 서비스 호출
Board board = service.selectBoard(map);
String path = null;
if (board != null) { // 조회 결과가 있을 경우
// ------------------------------------------------
// 현재 로그인 상태인 경우
// 로그인한 회원이 해당 게시글에 좋아요를 눌렀는지 확인
// ------------------------------------------------
if (loginMember != null) { // 로그인 상태인 경우
// 회원번호를 map에 추가
// map(boardCode, boardNo, memberNo)
map.put("memberNo", loginMember.getMemberNo());
// 좋아요 여부 확인 서비스 호출
int result = service.boardLikeCheck(map);
// 누른적이 있을 경우
if (result > 0)
model.addAttribute("likeCheck", "on");
}
// ------------------------------------------------
// 쿠키를 이용한 조회 수 증가 처리
// 1) 비회원 또는 로그인한 회원의 글이 아닌경우
if (loginMember == null || loginMember.getMemberNo() != board.getMemberNo()) {
// 비회원 // 내가쓴글X
// 2) 쿠키 얻어오기
Cookie c = null;
// 요청에 담겨있는 모든 쿠키 얻어오기
Cookie[] cookies = req.getCookies();
if (cookies != null) { // 쿠키가 존재할 경우
// 쿠키 중 "readBoardNo"라는 쿠키를 찾아서 c에 대입
for (Cookie cookie : cookies) {
if (cookie.getName().equals("readBoardNo")) {
c = cookie;
break;
}
}
}
// 3) 기존 쿠키가 없거나( c== null )
// 존재는 하나 현재 게시글 번호가 쿠키에 저장되지 않은 경우(오늘 해당 게시글을 본적 없음)
int result = 0;
if (c == null) {
// 쿠키가 존재 X -> 하나 새로 생성
c = new Cookie("readBoardNo", "|" + boardNo + "|");
// 조회수 증가 서비스 호출
result = service.updateReadCount(boardNo);
} else {
// 현재 게시글 번호가 쿠키에 있는지 확인
// Cookie.getValue() : 쿠키에 저장된 모든 값을 읽어옴
// -> String으로 반환
// String.indexOf("문자열")
// : 찾는 문자열이 몇번 인덱스에 존재하는지 반환
// 단, 없으면 -1 반환
if (c.getValue().indexOf("|" + boardNo + "|") == -1) {
// 쿠키에 현재 게시글 번호가 없다면
// 기존 값에 게시글 번호 추가해서 다시 세팅
c.setValue(c.getValue() + "|" + boardNo + "|");
// 조회수 증가 서비스 호출
result = service.updateReadCount(boardNo);
}
} // 3) 종료
// 4) 조회 수 증가 성공 시
// 쿠키가 적용되는 경로, 수명(당일 23시 59분 59초) 지정
if (result > 0) {
// 조회된 board 조회 수와 DB 조회 수 동기화
board.setReadCount(board.getReadCount() + 1);
// 적용 경로 설정
c.setPath("/"); // "/"이하 경로 요청 시 쿠키 서버로 전달
// 수명 지정
Calendar cal = Calendar.getInstance(); // 싱글톤 패턴
cal.add(cal.DATE, 1);
// 날짜 표기법 변경 객체 (DB의 TO_CHAR()와 비슷)
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
// java.util.Date
Date a = new Date(); // 현재 시간
Date temp = new Date(cal.getTimeInMillis()); // 내일 (24시간 후)
// 2023-08-24 12:17:40
Date b = sdf.parse(sdf.format(temp)); // 내일 0시 0분 0초
// 내일 0시 0분 0초 - 현재 시간
long diff = (b.getTime() - a.getTime()) / 1000;
// -> 내일 0시 0분 0초까지 남은 시간을 초단위로 반환
c.setMaxAge((int)diff); // 수명 설정
resp.addCookie(c); // 응답 객체를 이용해서 클라이언트에게 전달
}
}
// ------------------------------------------------
// forward할 jsp 경로
path = "board/boardDetail";
model.addAttribute("board", board);
} else { // 조회 결과가 없을 경우
// 게시판 첫 페이지로 리다이렉트
path = "redirect:/board/" + boardCode;
ra.addFlashAttribute("message", "해당 게시글이 존재하지 않습니다.");
}
return path;
}
@PostMapping("/like")
@ResponseBody // 반환되는 값이 비동기 요청한 곳으로 돌아가게 한다.
public int like(@RequestBody Map<String, Integer> paramMap) {
// System.out.println(paramMap);
return service.like(paramMap);
}
/** 좋아요 처리 서비스
* @param paramMap
* @return count
*/
int like(Map<String, Integer> paramMap);
/** 조회 수 증가 서비스
* @param boardNo
* @return
*/
int updateReadCount(int boardNo);
// 좋아요 처리 서비스
@Transactional(rollbackFor = Exception.class)
@Override
public int like(Map<String, Integer> paramMap) {
int result = 0;
if(paramMap.get("check") == 0) { // 좋아요 상태 X
// BOARD_LIKE 테이블 INSERT
result = dao.insertBoardLike(paramMap);
}else { // 좋아요 상태 O
// BOARD_LIKE 테이블 DELETE
result = dao.deleteBoardLike(paramMap);
}
// SQL 수행 결과가 0 == DB 또는 파라미터에 문제가 있다
// 1) 에러를 나타내는 임의의 값을 반환 (-1)
if(result == 0) return -1;
// 현재 게시글의 좋아요 개수 조회
int count = dao.countBoardLike(paramMap.get("boardNo"));
return count;
}
// 조회 수 증가 서비스
@Transactional(rollbackFor = Exception.class)
@Override
public int updateReadCount(int boardNo) {
return dao.updateReadCount(boardNo);
}
/** 좋아요 테이블 삽입
* @param paramMap
* @return result
*/
public int insertBoardLike(Map<String, Integer> paramMap) {
return sqlSession.insert("boardMapper.insertBoardLike", paramMap);
}
/** 좋아요 테이블 삭제
* @param paramMap
* @return result
*/
public int deleteBoardLike(Map<String, Integer> paramMap) {
return sqlSession.delete("boardMapper.deleteBoardLike", paramMap);
}
/** 좋아요 개수 조회
* @param boardNo
* @return count
*/
public int countBoardLike(Integer boardNo) {
return sqlSession.selectOne("boardMapper.countBoardLike", boardNo);
}
/** 조회수 증가
* @param boardNo
* @return result
*/
public int updateReadCount(int boardNo) {
return sqlSession.update("boardMapper.updateReadCount", boardNo);
}
<!-- 좋아요 테이블 삽입 -->
<insert id="insertBoardLike">
INSERT INTO BOARD_LIKE VALUES(#{boardNo}, #{memberNo})
</insert>
<!-- 좋아요 삭제 -->
<delete id="deleteBoardLike">
DELETE FROM BOARD_LIKE
WHERE BOARD_NO = #{boardNo}
AND MEMBER_NO = #{memberNo}
</delete>
<!-- 좋아요 개수 조회 -->
<select id="countBoardLike" resultType="_int">
SELECT COUNT(*) FROM BOARD_LIKE
WHERE BOARD_NO = #{boardNo}
</select>
<!-- 조회 수 증가 -->
<update id="updateReadCount">
UPDATE BOARD
SET READ_COUNT = READ_COUNT +1
WHERE BOARD_NO = #{boardNo}
</update>
쿠키를 이해해보려 노력해야겠다..이름은 귀엽게 생겨서 내용은 너무 어렵다ㅠㅠ 아직 잘 모르겠다😓
화이팅!!!!!!!!!!!!!!