이번엔 추천기능을 구현해보았다.
OKKY사이트처럼 추천을 하면 게시글에 추천수가 오르고, 게시글을 올린 회원의 추천 수가 올라가게 구현했다.
ajax도 사용해봤는데, 추천수를 카운트하는 부분을 따로 비동기방식으로 구현하기에는 이미 동기방식으로
구현해놓아서 필요성을 느끼지 못했다. 코드도 복잡해질테고..
그래서 중복방지기능만에 비동기방식을 사용하고, reload로 페이지를 새로고침해서 추천수를 갱신되게 구현했다.
현재 만들고 있는 게시판 프로젝트는 추천수를 기준으로 여러 혜택을 줄 수 있는 기능을 생각하고 있기 때문에,
추천수를 기준으로 정렬는 기준은 물론, 추천을 받은 기간을 나누기 위해서 date 타입의 컬럼도 넣어줬다.
구현한 기능은
- 추천버튼을 누르면 추천, 다시 누르면 추천취소
- 회원마다 게시글별 1개씩 추천가능(중복방지)
- 추천을 하면 해당 게시글을 올린 회원의 포인트(추천수) 가 1 증가, 취소시 1 감소
LIKE 테이블 생성
create table MP_LIKE(
LIKENO NUMBER NOT NULL PRIMARY KEY ,
BNO NUMBER,
MEMBER_ID VARCHAR2(50) NOT NULL,
RNO NUMBER,
LIKECHECK NUMBER DEFAULT 0 NOT NULL,
LIKEDATE DATE DEFAULT SYSDATE NOT NULL,
FOREIGN KEY (MEMBER_ID) REFERENCES MP_MEMBER(MEMBER_ID) ON DELETE CASCADE,
FOREIGN KEY (BNO) REFERENCES MP_BOARD (BNO) ON DELETE CASCADE
);
중복방지와 추천을 날짜별 등 구분할 때 필요한 테이블을 생성해준다.
먼저 게시판과 회원테이블이 필요하고, 게시판고유번호(BNO) 와 회원고유번호(MEMBER_ID)를 외래키로 잡아줬다.
on delete cascade는 부모키가 삭제되면 같이 삭제되는 기능이다.
일정기간 내에 가장 많은 추천을 받은 게시글이나 댓글을 표시하는 인기게시글, 인기댓글 등 기능을 구현했을때,
해당 게시글이 삭제되면 LIKE테이블도 같이 삭제되야 인기게시글이 갱신되기 때문에, 해당 제약조건이 필요하다.
RNO는 댓글고유번호인데, 댓글 추천기능은 게시글을 먼저 구현하고 후에 작업 할 예정이다.(외래키 지정해야함)
추천을 받음으로써 얻는 회원포인트 같은 경우는 회원테이블에 따로 저장되기 때문에,
게시글이(BNO) 삭제되도 영향을 받지 않는다.
***게시판 테이블에 LIKEHIT 컬럼 (NUMBER타입) 이 없다면 추가 ( 추천수 )
***회원 테이블에 POINT 컬럼 (NUMBER타입) 이 없다면 추가 (회원포인트)
Mapper - DAO - Service - Controller - JSP 로 구현했지만 DAO와 Service는 파라미터를 옮기는 역할만
수행했기 때문에 뒤에 한번에 코드만 올려놓으려고 한다.
Mapper
<!-- 게시글 추천수 -->
<update id="updateLike" parameterType="int">
update MP_BOARD set
LIKEHIT = LIKEHIT+1
where BNO = #{bno}
</update>
<!-- 게시글 추천수취소 -->
<update id="updateLikeCancel" parameterType="int">
update MP_BOARD set
LIKEHIT = LIKEHIT - 1
where BNO = #{bno}
</update>
<!-- 게시글 추천 시 Like 테이블에 insert -->
<insert id="insertLike">
insert into MP_LIKE(LIKENO , BNO , MEMBER_ID)
values((SELECT NVL(MAX(likeno), 0) + 1 FROM MP_LIKE) ,#{bno} ,#{memberId})
</insert>
<!-- 게시글 추천취소 시 delete -->
<delete id="deleteLike">
delete from MP_LIKE where BNO = #{bno} and MEMBER_ID = #{memberId}
</delete>
<!-- 게시글 추천 시 Check를 1로 만들어서 중복방지-->
<update id="updateLikeCheck">
update MP_LIKE set LIKECHECK = 1
where BNO = #{bno} and MEMBER_ID = #{memberId}
</update>
<!-- 게시글 추천취소 시 다시 0 -->
<update id="updateLikeCheckCancel">
update MP_LIKE set LIKECHECK = 0
where BNO = #{bno} and MEMBER_ID = #{memberId}
</update>
<!-- 게시글 추천 중복방지 select문 -->
<select id="likeCheck" resultType="int">
select count(*) from MP_LIKE where BNO = #{bno} and MEMBER_ID = #{memberId}
</select>
<!-- 게시글 추천 시 회원포인트 증가 -->
<update id="memberPointPlus">
update MP_MEMBER set MEMBER_POINT = MEMBER_POINT + 1 where MEMBER_ID = #{writerId}
</update>
<!-- 게시글 추천취소 시 회원포인트 감소 -->
<update id="memberPointDown">
update MP_MEMBER set MEMBER_POINT = MEMBER_POINT - 1 where MEMBER_ID = #{writerId}
</update>
기본적인 crud 구조라서 코드에 간단히 설명만 적어놓았다.
구현한 추천기능의 로직은
- 추천버튼 클릭
- ajax로 파라미터를 받음 -
- controller에서 Like테이블의 값을 Count해서 나온 값으로 추천인지 추천취소인지 구분
- 추천이면(Count값이 0) Like테이블에 값을 insert (insertLike)
- 추천수 +1 (updateLike)
- 게시글을 작성한 회원의 포인트 +1 (memberPointPlus)
++++(이미 구현을 하고, 글을 쓰면서 알았지만 LikeCheck에서,
select LikeCheck from MP_LIKE 가 아닌
select Count(*) from MP_LIKE를 하면 LikeCheck 컬럼이 필요가 없다는 걸 알았다,
위의 코드에서 updateLikeCheck 부분은 생략해도 되고, 그대로 진행해도 된다)++++
likeCheck로 Like테이블에 값이 있다면 (게시글고유번호와 회원고유번호) 1이 카운트되서
컨트롤러에서 0일때는 insert( update +1) , 1일때는 delete( update -1) 하는 구조이다.(뒤의 컨트롤러 부분 참고)
실력부족으로 인해 하드코딩이 되어버린 것 같다..
더 나은 방법이 분명 있겠지만, 일단 기능구현을 목표로 이렇게 구현해보았다.
Controller
@ResponseBody
@RequestMapping(value = "/board/updateLike" , method = RequestMethod.POST)
public int updateLike(int bno, String memberId,String writerId)throws Exception{
int likeCheck = service.likeCheck(bno, memberId);
if(likeCheck == 0) {
//좋아요 처음누름
service.insertLike(bno, memberId); //like테이블 삽입
service.updateLike(bno); //게시판테이블 +1
service.updateLikeCheck(bno, memberId);//like테이블 구분자 1
service.memberPointPlus(writerId); //회원포인트 +
}else if(likeCheck == 1) {
service.updateLikeCheckCancel(bno, memberId); //like테이블 구분자0
service.updateLikeCancel(bno); //게시판테이블 - 1
service.deleteLike(bno, memberId); //like테이블 삭제
service.memberPointDown(writerId); //회원포인트 -
}
return likeCheck;
}
비동기 통신을 위해 ResponBody 어노테이션을 선언해주고,
매개변수로 bno (게시글 고유값), memberId (회원고유값), writerId(글작성자의 회원고유값) 을 받는다.
**
현재 프로젝트에서 게시글 insert 시,
게시글 작성자는 회원닉네임으로 설정되어 있지만 (writer)
input hidden으로 ID를 넣어서 닉네임 변경시에도, 저장되있는 ID로 회원을 구분할 수 있게 구현해놓았음.
**
bno와 memberId로 Like테이블에 저장된 값이 있는지 확인하고 (count),
없다면( 0 ) 추천기능을,
있다면(1) 추천취소 기능을 수행하게 나눠주고
likeCheck값을 리턴해준다.
readView.jsp (게시글 상세보기뷰)
var bno = ${read.bno};
var memberId = ${login.memberId};
var writerId = ${read.id};
function updateLike(){
$.ajax({
type : "POST",
url : "/board/updateLike",
dataType : "json",
data : {'bno' : bno, 'memberId' : memberId , 'writerId' : writerId},
error : function(){
alert("통신 에러");
},
success : function(likeCheck) {
if(likeCheck == 0){
alert("추천완료.");
location.reload();
}
else if (likeCheck == 1){
alert("추천취소");
location.reload();
}
}
});
}
<div style="margin-right:1px;">
<button type="button" class="btn btn-warning " id="like_btn" onclick="updateLike(); return false;">추천 ${read.likehit}</button>
<button type="button" class="btn btn-danger" id="hate_btn">비추천</button>
</div>
추천수를 뷰 화면으로 리턴하는 부분은 추천수를 만드는 단계에서는 지나온 단계니 생략하도록 하겠다.
추천버튼을 클릭시
- 로그인을 통해 세션에 저장되어있는 회원의 값 (login.memberId)
- 게시글 고유번호 (read.bno)
- 게시글 작성자 ID(read.id)
를 ajax의 date로 보내주고,
0인지 1인지 구분해서 Controller에서 추천인지 취소인지 구분,
int값인 likeCheck를 리턴해서 0이면 추천완료 1이면 추천취소 팝업창이 뜨도록 구현했다.
간단하게 팝업창으로만 구분했지만,
0이나 1이냐로 button의 색상이나 표시되는 이미지를 바꿈으로 써 추천인지,
추천취소인지 깔끔하게 표시할 수도 있겠다. (후에 디자인을 손볼때 적용할 예정이다)
DAOImpl ( 코드가 길어서 DAO는 생략)
@Override
public void updateLike(int bno) throws Exception{
sqlsession.update("likeMapper.updateLike", bno);
}
@Override
public void updateLikeCancel(int bno) throws Exception{
sqlsession.update("likeMapper.updateLikeCancel", bno);
}
@Override
public void insertLike(int bno,String memberId) throws Exception{
Map<String,Object> map = new HashMap<String, Object>();
map.put("memberId", memberId);
map.put("bno", bno);
sqlsession.insert("likeMapper.insertLike", map);
}
@Override
public void deleteLike(int bno,String memberId)throws Exception{
Map<String,Object> map = new HashMap<String, Object>();
map.put("memberId", memberId);
map.put("bno", bno);
sqlsession.delete("likeMapper.deleteLike", map);
}
@Override
public int likeCheck(int bno,String memberId) throws Exception{
Map<String,Object> map = new HashMap<String, Object>();
map.put("memberId", memberId);
map.put("bno", bno);
return sqlsession.selectOne("likeMapper.likeCheck", map);
}
@Override
public void updateLikeCheck(int bno,String memberId) throws Exception{
Map<String,Object> map = new HashMap<String, Object>();
map.put("memberId", memberId);
map.put("bno", bno);
sqlsession.update("likeMapper.updateLikeCheck", map);
}
@Override
public void updateLikeCheckCancel(int bno,String memberId) throws Exception{
Map<String,Object> map = new HashMap<String, Object>();
map.put("memberId", memberId);
map.put("bno", bno);
sqlsession.update("likeMapper.updateLikeCheckCancel", map);
}
@Override
public void memberPointPlus(String writerId)throws Exception{
sqlsession.update("likeMapper.memberPointPlus", writerId);
}
@Override
public void memberPointDown(String writerId)throws Exception{
sqlsession.update("likeMapper.memberPointDown", writerId);
}
ServiceImpl
@Override
public void updateLike(int bno) throws Exception{
dao.updateLike(bno);
}
@Override
public void updateLikeCancel(int bno) throws Exception{
dao.updateLikeCancel(bno);
}
@Override
public void insertLike(int bno,String memberId) throws Exception{
dao.insertLike(bno, memberId);
}
@Override
public void deleteLike(int bno,String memberId)throws Exception{
dao.deleteLike(bno, memberId);
}
@Override
public int likeCheck(int bno,String memberId) throws Exception{
return dao.likeCheck(bno, memberId);
}
@Override
public void updateLikeCheck(int bno,String memberId)throws Exception{
dao.updateLikeCheck(bno, memberId);
}
@Override
public void updateLikeCheckCancel(int bno,String memberId)throws Exception{
dao.updateLikeCheckCancel(bno, memberId);
}
@Override
public void memberPointPlus(String writerId)throws Exception{
dao.memberPointPlus(writerId);
}
public void memberPointDown(String writerId)throws Exception{
dao.memberPointDown(writerId);
}
결과
- 작성자 11111 (ID도 11111) 이 올린 게시글
- 회원 1234 계정으로 추천버튼 누름
- 추천수가 증가했고, 다시 눌렀을때 추천취소
- 작성자 1111의 회원포인트가 2
- 회원 1234의 추천취소로 회원포인트가 1로 변경
일단 게시글 작성을 위해 비추천은 구현하지않고 간단하게 추천기능만 구현했다. ( 로직은 동일함 )
okky사이트처럼 추천과 비추천이 회원의 추천수(point)에 추가되는 기능을 따라서 구현해보았다.
게시글 추천수도 동일하게 추천과 비추천를 따로 나누지 않고 합산해서 구현할 수도 있겠지만,
난 따로 보이는게 커뮤니티 유저들의 의견이 더 잘보인다고 생각해서 나누었다.
+++
회원닉네임 옆에 추천수 등이 보이게 하기 위해선 join쿼리를 이용해서 member테이블의 값을 같이 select해야 한다.
'SPRING > IceWater Community' 카테고리의 다른 글
[스프링]게시판 읽은 글 표시 (3) | 2021.10.01 |
---|---|
[스프링] 공지사항 구현 (0) | 2021.09.30 |
[스프링] 회원정보수정 - 회원탈퇴 (ajax) (0) | 2021.09.25 |
[스프링] 회원정보수정 - 비밀번호 (ajax) (0) | 2021.09.24 |
[스프링] 회원정보수정 - 닉네임,이메일 (ajax) (0) | 2021.09.24 |