Spring 게시글 상세 조회 #2

밍드라이브·2023년 8월 23일
0

Spring

목록 보기
6/6
post-thumbnail

- jsp

누가(로그인한 회원 번호) 어떤 게시글(현재 게시글 번호) 좋아요를 클릭/취소
        
로그인한 회원 번호 얻어오기
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>

- 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{ // 좋아요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);
    }) // 예외 발생 시 처리하는 부분
});

- Controller

이제 요청 주소로 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);
	}

- BoardService interface

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

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

- BoardServiceImpl

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

BoardDAO

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

- Mapper

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

쿠키를 이해해보려 노력해야겠다..이름은 귀엽게 생겨서 내용은 너무 어렵다ㅠㅠ 아직 잘 모르겠다😓
화이팅!!!!!!!!!!!!!!

profile
민주입니다

0개의 댓글