본문 바로가기
학원/spring

1.19 (tiles-좋아요 구현, 댓글 기본 설정+ui)

by 쿠룽지 2024. 1. 21.
728x90
반응형

 

 

 

<복습>

 

 

Intercept 

공용으로 사용할 수 있는 폼을 설정할 때 주로 사용

로그인을 안했다? -> 로그인 체크 후 로그인 폼으로 보내기 등 작업 수행 가능
return 하는 방식이 redirect도 되고 forward방식으로도 가능 (우리는 redirect로 했음)

 

그후
LoginCheckInterceptor -> AppConfig (매핑/설정) 로 가서 설정함


 


 

 

 

 

<좋아요 표시>

 

 

 

Mapper에서 sql문 작성한 후 ServiceImpl에서 호출하기

ServiceImpl

@Override
public BoardFavVO selectFav(BoardFavVO fav) {
    return boardMapper.selectFav(fav);
}

@Override
public int selectFavCount(int board_num) {
    return boardMapper.selectFavCount(board_num);
}

 

 

 

 

 

 

 

 

컨트롤러에서 Ajax 넣기

BoardAjaxController

package kr.spring.board.controller;

import java.util.HashMap;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import kr.spring.board.service.BoardService;
import kr.spring.board.vo.BoardFavVO;
import kr.spring.board.vo.BoardVO;
import kr.spring.member.vo.MemberVO;
import kr.spring.util.FileUtil;
import lombok.extern.slf4j.Slf4j;

@Slf4j
@Controller
public class BoardAjaxController {
   @Autowired
   private BoardService boardService;
   
   /*=============================
    *    부모글 업로드 파일 삭제
    ============================*/
   @RequestMapping("/board/deleteFile")
   @ResponseBody
   public Map<String,String> processFile(int board_num, HttpSession session, HttpServletRequest request){
      Map<String,String> mapJson = new HashMap<String,String>();
     
      MemberVO user = (MemberVO)session.getAttribute("user");
      if(user==null) {
         mapJson.put("result", "logout");
      }else {
         //파일명 추출
         BoardVO vo = boardService.selectBoard(board_num);
         //삭제(DB)
         boardService.deleteFile(board_num);
         //업로드 경로에 있는 파일 삭제
         FileUtil.removeFile(request, vo.getFilename());
         
         mapJson.put("result", "success");
      }
      return mapJson;
   }
   
   
   /*=============================
    *    부모글 좋아요 읽기
    ============================*/
   @RequestMapping("/board/getFav")
   @ResponseBody
   public Map<String,Object> getFav(BoardFavVO fav, HttpSession session){
	   log.debug("<<게시판 좋아요 BoardFavVO>> : " + fav);
	  
	   Map<String,Object> mapJson = new HashMap<String,Object>();

	   //유저 구하기
	   MemberVO user = (MemberVO)session.getAttribute("user");
	   if(user==null) {
		   mapJson.put("status", "noFav");
	   }else {
		   //로그인된 회원번호 셋팅
		   fav.setMem_num(user.getMem_num());
		  
		   BoardFavVO boardFav = boardService.selectFav(fav); //user에게 있는지 없는지 알려줌
		   if(boardFav!=null) { //해당 user가 좋아요를 눌렀을 때
			   mapJson.put("status", "yesFav");
		   }else {
			   mapJson.put("status", "noFav");
		   }
	   }
	   //좋아요 수
	   mapJson.put("count", boardService.selectFavCount(fav.getBoard_num()));
	  
	   return mapJson;
   }
}

 

1.

if(user==null){ // 로그인한 유저가 아닐 경우 >> status=noFav

 

2.

else{

(boardFav!=null) 로그인한 유저가 좋아요를 눌렀을 때 >> yesFav
누르지 않았을 때 >> noFav

 

 

 

 

 

 

 

 

mbox에서 내려받은 좋아요 사진 static.images에 넣기

사진 폴더에 넣을 때에는 contextroot로 인식해서 파일 경로 만들 때에 webapp에 함

but 이미 서버에 있는 링크는 static으로 인식, 그래서 static.images로 빼온다 함

 

 

 

 

 

 

js파일 만들기

static

js

board.fav.js (javaScript로 생성)

$(function(){
	/*---------------------------
	 *      좋아요 읽기
	 *---------------------------*/
     //좋아요 선택 여부와 선택한 총 개수를 표시
     function selectFav(board_num){
     	$.ajax({
        	url:'getFav',
            type:'post',
            data:{board_num:board_num},
            dataType:'json',
            success:function(param){
            	displayFav(param);
            },
            error:function(){
            	alert('네트워크 오류');
            }
        });
     }
     
     /*---------------------------
	 *      좋아요 표시 공통 함수
	 *---------------------------*/
	function displayFav(param){
		let output;
		if(param.status == 'yesFav'){
			output = '../images/fav02.gif';
		}else if(param.status == 'noFav'){
			output = '../images/fav01.gif';
		}else{
			alert('좋아요 표시 오류 발생');
		}
		
		//문서 객체에 정보 추가
		$('#output_fav').attr('src',output);
		$('#output_fcount').text(param.count);
	}

	//초기 데이터 표시
	//맨 위에서 명시한 메서드 호출
	selectFav($('#output_fav').attr('data-num'));
});

selectFav
data:{board_num:board_num} >> 데이터로 board_num을 보내는 것 (데이터 관련해서는 {} 씀)
dataType:'json' >> 반환받는 데이터 타입이 json
success 시 displayFav(param); 호출

==============================

displayFav
param에 값이 전달되면 status와 count가 날아와서 status로 처리,
$('#output_fav').attr('src',output); >> 상황에 따라 이미지 보이기
$('#output_fcount').text(param.count); >> count (좋아요 수) 화면에 표시

==============================

selectFav($('#output_fav').attr('data-num'));
#output_fav >> 커스텀 태그를 넣어서 board_num값을 읽고 표시하려고 그런다함 (?)..

 

 

 

 

 

 

 

 

상세페이지 수정

boardView.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %>
<!-- 내용 시작 -->
<script type="text/javascript" src="${pageContext.request.contextPath}/js/jquery-3.6.0.min.js"></script>
<script type="text/javascript" src="${pageContext.request.contextPath}/js/videoAdapter.js"></script>
<script type="text/javascript" src="${pageContext.request.contextPath}/js/board.fav.js"></script>
<div class="page-main">
	<h2>${board.title}</h2>
	<ul class="detail-info">
		<li>
			<img src="${pageContext.request.contextPath}/member/viewProfile?mem_num=${board.mem_num}" width="40" height="40" class="my-photo">
		</li>
		<li>
			<c:if test="${empty board.nick_name}">${board.id}</c:if>
			<c:if test="${!empty board.nick_name}">${board.nick_name}</c:if>
			<br>
			<c:if test="${!empty board.modify_date}">
			최근 수정일 : ${board.modify_date}
			</c:if>
			<c:if test="${empty board.modify_date}">
			작성일 : ${board.reg_date}
			</c:if>
			조회 : ${board.hit}
		</li>
	</ul>
	<c:if test="${!empty board.filename}">
	<ul>
		<li>첨부파일 : <a href="file?board_num=${board.board_num}">${board.filename}</a></li>
	</ul>
	</c:if>
	<hr size="1" width="100%">
	<c:if test="${fn:endsWith(board.filename, '.jpg') ||
				  fn:endsWith(board.filename, '.JPG') ||
				  fn:endsWith(board.filename, '.jpeg') ||
				  fn:endsWith(board.filename, '.JPEG') ||
				  fn:endsWith(board.filename, '.gif') ||
				  fn:endsWith(board.filename, '.GIF') ||
				  fn:endsWith(board.filename, '.png') ||
				  fn:endsWith(board.filename, '.PNG')}">
	<div class="align-center">
		<img src="${pageContext.request.contextPath}/upload/${board.filename}" class="detail-img">
	</div>
	</c:if>
	<div class="detail-content">
		${board.content}
	</div>
	<div>
		<%-- 좋아요 --%>
		<img id="output_fav" data-num="${board.board_num}" src="${pageContext.request.contextPath}/images/fav01.gif" width="40">
		<span id="output_fcount"></span>
		<%-- 댓글수 --%>
	</div>
	<hr size="1" width="100%">
	<div class="align-right">
		<c:if test="${!empty user && user.mem_num == board.mem_num}">
			<input type="button" value="수정" onclick="location.href='update?board_num=${board.board_num}'">
			<input type="button" value="삭제" id="delete_btn">
			<script type="text/javascript">
				let delete_btn = document.getElementById('delete_btn');
				delete_btn.onclick=function(){
					let choice = confirm('삭제하시겠습니까?');
					if(choice){
						location.href='delete?board_num=${board.board_num}'
					}
				};
			</script>
		</c:if>
		<input type="button" value="목록" onclick="location.href='list'">
	</div>
	<hr size="1" width="100%">
	<%-- 댓글 작성 --%>
</div>
<!-- 내용 끝 -->

1. 

script 추가 > 아까 생성한 board.jav.js

 

2.

좋아요 부분 코드 작성

 

==============================
실행하면 빈 하트+0 이 보임

페이지 우클릭 > 검사 >
1) 콘솔에 에러가 있는지 없는지
2) network - 페이로드 board_num
                미리보기 count status가 보임

 

 

 

 

 

 

좋아요 스타일 추가

css

layout.css

/* 좋아요 */
#output_fav{
	cursor:pointer;
}
#output_fcount{
	width:50px;
	display:inline-block;
}

 



 


 

 

 

 

<좋아요 기능 구현>

 

 

 

Mapper에서 sql문 작성

BoardMapper.java

insert, delete 코드 작성

@Insert("INSERT INTO spboard_fav (board_num,mem_num) VALUES (#{board_num},#{mem_num})")
public void insertFav(BoardFavVO fav);
@Delete("DELETE FROM spboard_fav WHERE board_num=#{board_num} AND mem_num=#{mem_num}")
public void deleteFav(BoardFavVO boardFav);

 

 

 

 

 

서비스 파일에서 호출

BoardServiceImpl

@Override
public void insertFav(BoardFavVO fav) {
    boardMapper.insertFav(fav);
}

@Override
public void deleteFav(BoardFavVO boardFav) {
    boardMapper.deleteFav(boardFav);
}

 

 

 

 

 

컨트롤러 등록

BoardAjaxController

/*=============================
*    부모글 좋아요 등록/ 삭제
* ============================*/
@RequestMapping("/board/writeFav")
@ResponseBody
public Map<String,Object> writeFav(BoardFavVO fav, HttpSession session){
   log.debug("<<부모글 좋아요 등록/삭제 BoardFavVO>> : " + fav);

   Map<String,Object> mapJson = new HashMap<String,Object>();

   //로그인 상태 확인 후 동작 처리
   MemberVO user = (MemberVO)session.getAttribute("user");
   if(user==null) {
       mapJson.put("result", "logout");
   }else {
       //로그인된 회원번호 셋팅
       fav.setMem_num(user.getMem_num());

       //이전에 좋아요를 등록했는지 여부 확인 (토글 형태)
       BoardFavVO boardFavVO = boardService.selectFav(fav);
       if(boardFavVO!=null) { //좋아요를 이미 등록
           boardService.deleteFav(fav);
           mapJson.put("status", "noFav");
       }else {//좋아요 미등록
           boardService.insertFav(fav);
           mapJson.put("status", "yesFav");
       }
       mapJson.put("result", "success");
       mapJson.put("count", boardService.selectFavCount(fav.getBoard_num()));
   }
   return mapJson;
}

 

컨트롤러의 @RequestMapping("/board/writeFav") --> js파일의 url 명과 동일

 

 

 

 

 

 

js파일 수정

board.jav.js

/*---------------------------
 *      좋아요 등록/삭제
 *---------------------------*/
$('#output_fav').click(function(){
    $.ajax({
        url:'writeFav',
        type:'post',
        data:{board_num:$('#output_fav').attr('data-num')},
        dataType:'json',
        success:function(param){
            if(param.result == 'logout'){
                alert('로그인 후 좋아요를 눌러주세요');
            }else if(param.result == 'success'){
                displayFav(param);
            }else{
                alert('등록/삭제 시 오류 발생');
            }
        },
        error:function(){
            alert('네트워크 오류!');
        }
    });
});

data:{board_num:$('#output_fav').attr('data-num')}
data-num에 board_num을 넣어놨는데 data-num이라는 설정 태그로부터 데이터를 읽어와서 전송한다는 뜻

==============================

하고 실행하면
비로그인 시 --> 로그인 후 좋아요를 눌러주세요
로그인 후 좋아요 체크 정상적으로 작동
정렬도 좋아요 순으로 정렬이 됨

 

 

 

 

 


 

 

 

 

 

<댓글 기본 설정>

 

 

 

 

 

 

테이블 생성하기

table.sql

--게시판 댓글
create table spboard_reply(
 re_num number not null,
 re_content varchar2(900) not null,
 re_date date default sysdate not null,
 re_mdate date, --글 수정시 사용
 re_ip varchar2(40) not null,
 board_num number not null,
 mem_num number not null,
 constraint spboard_reply_pk primary key (re_num),
 constraint spboard_reply_fk1 foreign key (board_num) references spboard (board_num),
 constraint spboard_reply_fk2 foreign key (mem_num) references spmember (mem_num)
);

create sequence spreply_seq;

댓글도 vo, servicedao, controller, ajax통신 진행하는 과정 모두 똑같음

 

 

 

 

 

 

 

VO 생성하기

kr.spring.board.vo
BoardReplyVO (class)

package kr.spring.board.vo;

import lombok.Getter;
import lombok.Setter;
import lombok.ToString;

@Getter
@Setter
@ToString
public class BoardReplyVO {
	private int re_num;
	private String re_content;
	private String re_date;
	private String re_mdate; //연월일시분초가 아니라 하루전 이틀전 이렇게 변환 작업할거라서 string 처리
	private String re_ip;
	private int board_num;
	private int mem_num;
	
	private String id;
	private String nick_name;
}

 

 

 

 

 

 

 

 

 

댓글 부분 DAO 작성

BoardMapper.java 그대로 사용

//댓글
public List<BoardReplyVO> selectListReply(Map<String,Object> map);
public int selectRowCountReply(Map<String,Object> map);
public BoardReplyVO selectReply(int re_num); //한건의 데이터
public void insertReply(BoardReplyVO boardReply);
public void updateReply(BoardReplyVO boardReply);
public void deleteReply(int re_num);
@Delete("DELETE FROM spboard_reply WHERE board_num=#{board_num}")
public void deleteReplyByBoardNum(int board_num); //부모글 삭제 시 댓글이 존재하면 부모글 삭제 전 댓글 삭제

댓글도 부모글 지울 때 자식글도 같이 지워야 오류가 안남

그래서 좋아요와 똑같이 deleteReplyByBoardNum 메서드 추가

 

 

 

 

 

 

 

 

부모글 삭제 메서드에 댓글 삭제도 추가하기

BoardServiceImpl

@Override
public void deleteBoard(int board_num) {
    //부모글 좋아요 삭제
    boardMapper.deleteFavByBoardNum(board_num);
    //댓글이 존재하면 댓글을 우선 삭제하고 부모글 삭제
    boardMapper.deleteReplyByBoardNum(board_num);
    //부모글 삭제
    boardMapper.deleteBoard(board_num);
}

 

 

 

 

 

 

 

게시판 정렬 댓글순 코드도 추가

BoardMapper.xml

<!-- 게시판 전체 목록/검색 목록 -->
<select id="selectList" parameterType="map" resultType="boardVO">
    SELECT
      *
    FROM (SELECT
            a.*,
            rownum rnum
        FROM (SELECT
                board_num,
                <![CDATA[
                REPLACE(REPLACE(title,'<','&lt;'),'>','&gt;') title,
                ]]>
                hit,
                reg_date,
                mem_num,
                id,
                nick_name,
                re_cnt,
                fav_cnt
              FROM spboard
              LEFT OUTER JOIN (SELECT COUNT(*) re_cnt, board_num FROM spboard_reply GROUP BY board_num) USING(board_num)
              LEFT OUTER JOIN (SELECT COUNT(*) fav_cnt, board_num FROM spboard_fav GROUP BY board_num) USING(board_num)
              JOIN spmember USING(mem_num)
              <include refid="boardSearch"></include>
              <include refid="boardOrder"></include>)a)
    <![CDATA[
    WHERE rnum >= #{start} AND rnum <= #{end}
    ]]>
</select>

re_cnt, left outer join 코드 추가

 

정렬 1~4 (최신순~댓글수)
다 테스트 해보고 에러만 안나면 됨
댓글 준비 완료

 

 


 

 

 

 

 

<댓글창 표시하기>

 

 

 

 

서비스 파일에 Mapper에서 설정한 dao 붙여넣기

BoardService,java

//댓글
public List<BoardReplyVO> selectListReply(Map<String,Object> map);
public int selectRowCountReply(Map<String,Object> map);
public BoardReplyVO selectReply(int re_num); //한건의 데이터
public void insertReply(BoardReplyVO boardReply);
public void updateReply(BoardReplyVO boardReply);
public void deleteReply(int re_num);

 

 

 

 

 

 

BoardServiceImpl >> 에러창 뜰텐데 그럼 눌러서 메서드 추가하면 됨

mbox에서 로딩 이미지 내려받고 static images 안에 붙여넣기

 

 

 

 

상세 화면 UI 변경

views

board

boardView.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %>
<!-- 내용 시작 -->
<script type="text/javascript" src="${pageContext.request.contextPath}/js/jquery-3.6.0.min.js"></script>
<script type="text/javascript" src="${pageContext.request.contextPath}/js/videoAdapter.js"></script>
<script type="text/javascript" src="${pageContext.request.contextPath}/js/board.fav.js"></script>
<div class="page-main">
	<h2>${board.title}</h2>
	<ul class="detail-info">
		<li>
			<img src="${pageContext.request.contextPath}/member/viewProfile?mem_num=${board.mem_num}" width="40" height="40" class="my-photo">
		</li>
		<li>
			<c:if test="${empty board.nick_name}">${board.id}</c:if>
			<c:if test="${!empty board.nick_name}">${board.nick_name}</c:if>
			<br>
			<c:if test="${!empty board.modify_date}">
			최근 수정일 : ${board.modify_date}
			</c:if>
			<c:if test="${empty board.modify_date}">
			작성일 : ${board.reg_date}
			</c:if>
			조회 : ${board.hit}
		</li>
	</ul>
	<c:if test="${!empty board.filename}">
	<ul>
		<li>첨부파일 : <a href="file?board_num=${board.board_num}">${board.filename}</a></li>
	</ul>
	</c:if>
	<hr size="1" width="100%">
	<c:if test="${fn:endsWith(board.filename, '.jpg') ||
				  fn:endsWith(board.filename, '.JPG') ||
				  fn:endsWith(board.filename, '.jpeg') ||
				  fn:endsWith(board.filename, '.JPEG') ||
				  fn:endsWith(board.filename, '.gif') ||
				  fn:endsWith(board.filename, '.GIF') ||
				  fn:endsWith(board.filename, '.png') ||
				  fn:endsWith(board.filename, '.PNG')}">
	<div class="align-center">
		<img src="${pageContext.request.contextPath}/upload/${board.filename}" class="detail-img">
	</div>
	</c:if>
	<div class="detail-content">
		${board.content}
	</div>
	<div>
		<%-- 좋아요 --%>
		<img id="output_fav" data-num="${board.board_num}" src="${pageContext.request.contextPath}/images/fav01.gif" width="40">
		<span id="output_fcount"></span>
		<%-- 댓글수 --%>
		<span id="output_rcount"></span>
	</div>
	<hr size="1" width="100%">
	<div class="align-right">
		<c:if test="${!empty user && user.mem_num == board.mem_num}">
			<input type="button" value="수정" onclick="location.href='update?board_num=${board.board_num}'">
			<input type="button" value="삭제" id="delete_btn">
			<script type="text/javascript">
				let delete_btn = document.getElementById('delete_btn');
				delete_btn.onclick=function(){
					let choice = confirm('삭제하시겠습니까?');
					if(choice){
						location.href='delete?board_num=${board.board_num}'
					}
				};
			</script>
		</c:if>
		<input type="button" value="목록" onclick="location.href='list'">
	</div>
	<hr size="1" width="100%">
	<!-- 댓글 시작 -->
	<div id="reply_div">
		<span class="re-title">댓글 달기</span>
		<form id="re_form">
			<input type="hidden" name="board_num" value="${board.board_num}" id="board_num">
			<textarea rows="3" cols="50" name="re_content" id="re_content" class="rep-content"
			<c:if test="${empty user}">disabled="disabled"</c:if>
			><c:if test="${empty user}">로그인해야 작성할 수 있습니다.</c:if></textarea>
			<c:if test="${!empty user}">
			<div id="re_first">
				<span class="letter-count">300/300</span>
			</div>
			<div id="re_second" class="align-right">
				<input type="submit" value="전송">
			</div>
			</c:if>
		</form>
	</div>
	<!-- 댓글 목록 출력 시작 -->
	<div id="output"></div>
	<div class="paging-button" style="display:none;">
		<input type="button" value="더보기">
	</div>
	<div id="loading" style="display:none;"> <%-- 로딩바 생성 --%>
		<img src="${pageContext.request.contextPath}/images/loading.gif" width="100" height="100">
	</div>
	<!-- 댓글 목록 출력 끝 -->
	<!-- 댓글 끝 -->
</div>
<!-- 내용 끝 -->

<c:if test="${empty user}">disabled="disabled"</c:if>
로그인하지 않았을 때엔 댓글 작성 칸 비활성화

 

 

 

 

 

 

스타일 처리

layout.css

/* 댓글 */
div#reply_div{
	padding:5px 10px 40px 10px;
	margin-top:10px;
	background-color:#eeeeee;
}
form#re_form{
	width:650px;
	border:none;
}
span.re-title{
	color:#000;
	font-size:12pt;
	line-height:200%;
}
span.letter-count{
	font-size:10pt;
	color:#999;
}
textarea.rep-content{
	width:90%;
	height:50px;
	margin:10px 10px;
}
div#re_first,div#mre_first{
	float:left;
	width:70%;
	padding-left:15px;
	margin-bottom:10px;
}
div#loading{
	width:100px;
	height:50px;
	/* 정중앙에 div를 배치하기 위한 설정 */
	position:absolute;
	top:50%;
	left:50%;
	transform:translate(-50%,-50%);
	/* 하위요소를 수직으로 쌓을 수 있는 공간 만들기 - flex*/
	display:flex;
	/* 세로 정렬 */
	align-items:center;
	/* 가로 정렬 */
	justify-content:center;
}
div.paging-button{
	text-align:right;
}
div#output{
	clear:both;
}
form#mre-form{
	border:none;
	margin:5px;
}

실행 후 상세페이지 들어갔을 때


비로그인 상태 -> 댓글창 비활성화 / 로그인하라고 메시지 뜸
로그인 상태 -> 댓글창 활성화

 

 

 


 

 

 

 

 

<댓글 등록하기>

 

 

 

 

dao파일에 sql문 작성하기

BoardMapper.xml

(insert가 좀 길어서 xml 파일에 기재)

<!-- 댓글 등록 -->
<insert id="insertReply" parameterType="boardReplyVO">
INSERT INTO spboard_reply(
    re_num,
    re_content,
    re_ip,
    board_num,
    mem_num)
VALUES (
    spreply_seq.nextval,
    #{re_content},
    #{re_ip},
    #{board_num},
    #{mem_num})
</insert>

 

 

 

 

 

 

Service파일에 호출하기

BoardServiceImpl

@Override
public void insertReply(BoardReplyVO boardReply) {
    boardMapper.insertReply(boardReply);
}

 

728x90
반응형