좋아요 기능, 조회수 증가 기능

oYJo·2025년 3월 13일

Spring

목록 보기
22/29

좋아요 기능

1️⃣ Visual Studio Code

✔️webapp(WEB-INF(views(board))) → boardDetail.jsp

<!-- 좋아요 하트 -->
<span class="like-area">

  <!-- 좋아요를 누른 적이 없거나, 로그인 X -->
  <c:if test="${empty likeCheck}">
    <i class="fa-regular fa-heart" id="boardLike"></i>
  </c:if>

  <!-- 좋아요를 누른 적이 있을 때 -->
  <c:if test="${!empty likeCheck}">
    <i class="fa-solid fa-heart" id="boardLike"></i>
  </c:if>

  <span>${board.likeCount}</span>
</span>
<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>

✔️webapp(resources(js((board))) → boardDetail.js

// 좋아요 버튼이 클릭되었을 때
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{ // 좋아요 체크 O (꽉찬하트)
    check = 1;
  }

  // ajax로 서버로 제출할 파라미터를 모아둔 JS 객체
  // 로그인한 회원 번호, 게시글 번호, check
  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);

  }) // 예외 발생 시 처리하는 부분
})

2️⃣ Spring

✔️project(board(controller)) → BoardController.java

...

		// 게시글 상세 조회 서비스 호출
		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");
			}
            
            // ------------------------------------------------
            
            			path = "board/boardDetail"; // forward 할 jsp 경로
			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);
	}

✔️project(board(model(service))) → BoardService.java

...
	/** 좋아요 여부 확인
	 * @param map
	 * @return result
	 */
	int boardLikeCheck(Map<String, Object> map);

	/** 좋아요 처리 서비스
	 * @param paramMap
	 * @return count
	 */
	int like(Map<String, Integer> paramMap);

✔️project(board(model(service))) → BoardServiceImpl.java

...
	// 좋아요 여부 확인 서비스
	@Override
	public int boardLikeCheck(Map<String, Object> map) {
		return dao.boardLikeCheck(map);
	}

	// 좋아요 처리 서비스
	@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;
		
	}

✔️project(board(model(dao))) → BoardDAO.java

...
	/** 좋아요 여부 확인
	 * @param map
	 * @return result
	 */
	public int boardLikeCheck(Map<String, Object> map) {
		return sqlSession.selectOne("boardMapper.boardLikeCheck", map);
	}

	/** 좋아요 테이블 삽입
	 * @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);
	}

✔️resources(mappers) → board-mapper.xml

...
<!-- 좋아요 여부 확인 -->
<select id="boardLikeCheck" resultType="_int">
  SELECT COUNT(*) FROM BOARD_LIKE
  WHERE BOARD_NO = #{boardNo}
  AND MEMBER_NO = #{memberNo}
</select>

<!-- 좋아요 테이블 삽입 -->
<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>

조회수 증가 기능

⭐️ 쿠키로 조회수 증가시키기

1️⃣ Spring

✔️project(board(controller)) → BoardController.java

...
	// 게시글 상세 조회
	@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) { // 조회 결과가 있을 경우
			
		...

			// 쿠키를 이용한 조회 수 증가 처리
			
			// 1) 비회원 또는 로그인한 회원의 글이 아닌 경우
			if(loginMember == null || // 비회원
					board.getMemberNo() != loginMember.getMemberNo()) {
				
				// 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 -> 하나 새로 생성
					// 톰캣 8.5 이상부터는 쿠키에 >> , / = 띄어쓰기 << 작성 불가
					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); // 응답 객체를 이용해서 클라이언트에게 전달
				}
			}
			
			path = "board/boardDetail"; // forward 할 jsp 경로
			model.addAttribute("board", board);
			
		} else { // 조회 결과가 없을 경우
			path = "redirect:/board/" + boardCode; // 게시판 첫 페이지로 리다이렉트
			
			ra.addFlashAttribute("message", "해당 게시글이 존재하지 않습니다.");
		}
		
		return path;
	}
...


date버전이 너무 별로라서 calendar.getl으로 사용
= 더 업그레이드 버전으로 나옴


Calendar cal = Calendar.getInstance(); // 싱글톤 패턴 // 오늘 날짜 나옴
cal.add(cal.DATE, 1); // 하루 더했으니깐 내일

✔️project(board(model(service))) → BoardService.java

...
	/** 조회수 증가 서비스
	 * @param boardNo
	 * @return
	 */
	int updateReadCount(int boardNo);

✔️project(board(model(service))) → BoardServiceImpl.java

...
	// 조회 수 증가 서비스
	@Transactional(rollbackFor = Exception.class)
	@Override
	public int updateReadCount(int boardNo) {
		return dao.updateReadCount(boardNo);
	}

✔️project(board(model(dao))) → BoardDAO.java

...
	/** 조회 수 증가
	 * @param boardNo
	 * @return result
	 */
	public int updateReadCount(int boardNo) {
		return sqlSession.update("boardMapper.updateReadCount", boardNo);
	}

✔️resources(mappers) → board-mapper.xml

...
<update id="updateReadCount">
  UPDATE BOARD SET
  READ_COUNT = READ_COUNT + 1
  WHERE BOARD_NO = #{boardNo}
</update>

import할 때 util로 가져오기






profile
Hello! My Name is oYJo

0개의 댓글