지구정복

[2021_팀. 랜선여행 웹 커뮤니티 프로젝트] 본문

프로젝트

[2021_팀. 랜선여행 웹 커뮤니티 프로젝트]

eeaarrtthh 2021. 4. 20. 11:57
728x90
반응형

-GitHub 링크 : github.com/JiHooney/Want

 

#목차

1. 배경 및 기획

2. 개발 상세화

3. 개발 과정

4. 기타


1. 배경 및 기획

더보기

-기획 프레젠테이션(프레지) : prezi.com/view/AE8KLVImAgkIYKXKe0Pu/

 

 

#팀소개

KIC캠퍼스 김해선(팀장), 이지훈, 정현수, 박혁준

 

 

#배경

 

코로나로 인해 해외여행을 하지못하여 스트레스받는 사람들 혹은 유럽여행관련 정보를 얻고 싶은 예비 여행자들을 위한 랜선여행 웹서비스를 제작

 

 

#기획

-랜선여행 웹서비스 메뉴도 ( 최종 메뉴도는 수정됨 )

 

-간트차트(기간은 약 한 달)

 

 

#기술스택

-개발언어: HTML, CSS(Bootstrap), JavaScript(Ajax, jQuery), Java(Spring Freamwork MVC Model2, Mybatis), JSP, SQL

-운영체제: Window10, Linux centOS

-웹 서버: Apache-tomcat
-클라우드: NCloud

-데이터베이스: MariaDB

 

 

#내 역할

--기획단계--

기획 PPT 제작

시퀀스 다이어그램 제작
테이블 명세서 제작

 

--개발단계--
회원가입 유효성검사

비밀번호 암호화 및 복호화

로그인 기능 구현

쇼핑&숙소 게시판 제작

    (게시판 CRUD, 댓글 및 답글, 좋아요기능, 무한스크롤 기능, 검색 기능)

전세계 주요도시 날씨 데이터 크롤링

내 프로필 구현

좋아요 목록 구현

클라우드 서버 웹호스팅

 

 

 

 

 


2. 개발 상세화

#유스케이스, ERD, 테이블 명세서, 시퀀스 다이어그램

drive.google.com/file/d/1c-hCkz-V_rC4eY6rrNrukk4YuxoeamEx/view?usp=sharing

 

 

 

 

 


3. 개발 과정

모든 코드는 맨 위 깃헙 링크에 들어가면 확인할 수 있다.


#SQL(DDL)

더보기
#######유저관련 DDL#########
#유저 테이블(user)
create table user (
id varchar(100) primary key,
pwd varchar(100) not null,
name varchar(20) not null,
birth varchar(20) not null,
mail varchar(100) not null,
phone varchar(20) not null,
nick varchar(50) unique key not null,
rdate datetime not null,
profile varchar(100) default 'default_profile.png' ,
greet varchar(1000) default '안녕하세요'
);

#######게시판관련 DDL#########
#랜선여행테이블(l_board)
create table l_board(
no int auto_increment primary key,
subject varchar(100) not null,
content varchar(2000) not null,
writer varchar(100) not null,
wdate datetime not null,
hit int not null,
location varchar(20) not null,
video varchar(200),
reply int not null,
heart int not null,
constraint l_writer_fk foreign key(writer) references user(nick) 
on delete cascade on update cascade
);

#랜선여행 신청 테이블(la_board)
create table la_board(
no int auto_increment primary key,
subject varchar(100) not null,
content varchar(2000) not null,
writer varchar(100) not null,
wdate datetime not null,
hit int not null,
location varchar(20) not null,
picture varchar(200),
reply int not null,
constraint la_writer_fk foreign key(writer) references user(nick) 
on delete cascade on update cascade
);

#사진자랑 테이블(p_board)
create table p_board(
no int auto_increment primary key,
subject varchar(100) not null,
content varchar(2000) not null,
writer varchar(100) not null,
wdate datetime not null,
hit int not null,
location varchar(20) not null,
media varchar(200),
reply int not null,
heart int not null,
constraint p_writer_fk foreign key(writer) references user(nick) 
on delete cascade on update cascade
);

#쇼핑정보 테이블(s_board)
create table s_board(
no int auto_increment primary key,
subject varchar(100) not null,
content varchar(2000) not null,
writer varchar(100) not null,
wdate datetime not null,
hit int not null,
location varchar(20) not null,
picture varchar(200),
reply int not null,
heart int not null,
constraint s_writer_fk foreign key(writer) references user(nick) 
on delete cascade on update cascade
);

#숙소정보 테이블(a_board)
create table a_board(
no int auto_increment primary key,
subject varchar(100) not null,
content varchar(2000) not null,
writer varchar(100) not null,
wdate datetime not null,
hit int not null,
location varchar(20) not null,
picture varchar(200),
reply int not null,
heart int not null,
constraint a_writer_fk foreign key(writer) references user(nick) 
on delete cascade on update cascade
);

#동행 구해요 테이블 (w_board)
create table w_board(
no int auto_increment primary key,
subject varchar(100) not null,
content varchar(2000) not null,
writer varchar(100) not null,
wdate datetime not null,
hit int not null,
location varchar(20) not null,
picture varchar(200),
reply int not null,
constraint w_writer_fk foreign key(writer) references user(nick) 
on delete cascade on update cascade
);


#######댓글 관련 DDL#########
#랜선여행 댓글 테이블(l_reply)
create table l_reply(
no int auto_increment primary key,
bno int not null,
writer varchar(100) not null,
content varchar(1000),
wdate datetime not null,
grp int not null,
grps int not null,
grpl int not null,
constraint l_r_writer_fk foreign key(writer) references user(nick) 
on delete cascade on update cascade,
constraint l_r_bno_fk foreign key(bno) references l_board(no) 
on delete cascade
);

#랜선여행 신청 댓글 테이블(la_reply)
create table la_reply(
no int auto_increment primary key,
bno int not null,
grp int not null,
grps int not null,
grpl int not null,
writer varchar(100) not null,
content varchar(1000),
wdate datetime not null,
constraint la_r_writer_fk foreign key(writer) references user(nick) 
on delete cascade on update cascade,
constraint la_r_bno_fk foreign key(bno) references la_board(no) 
on delete cascade
);

#사진자랑 게시판 댓글 테이블(p_reply)
create table p_reply(
no int auto_increment primary key,
bno int not null,
grp int not null,
grps int not null,
grpl int not null,
writer varchar(100) not null,
content varchar(1000),
wdate datetime not null,
constraint p_r_writer_fk foreign key(writer) references user(nick) 
on delete cascade on update cascade,
constraint p_r_bno_fk foreign key(bno) references p_board(no) 
on delete cascade
);

#쇼핑정보 댓글 테이블(s_reply)
create table s_reply(
no int auto_increment primary key,
bno int not null,
writer varchar(100) not null,
content varchar(1000),
wdate datetime not null,
grp int not null,
grps int not null,
grpl int not null,
constraint s_r_writer_fk foreign key(writer) references user(nick) 
on delete cascade on update cascade,
constraint s_r_bno_fk foreign key(bno) references s_board(no) 
on delete cascade
);

#숙소 정보 댓글 테이블(a_reply)
create table a_reply(
no int auto_increment primary key,
bno int not null,
writer varchar(100) not null,
content varchar(1000),
wdate datetime not null,
grp int not null,
grps int not null,
grpl int not null,
constraint a_r_writer_fk foreign key(writer) references user(nick) 
on delete cascade on update cascade,
constraint a_r_bno_fk foreign key(bno) references a_board(no) 
on delete cascade
);

#동행구해요 댓글 테이블 (w_reply)
create table w_reply(
no int auto_increment primary key,
bno int not null,
grp int not null,
grps int not null,
grpl int not null,
writer varchar(100) not null,
content varchar(1000),
wdate datetime not null,
constraint w_r_writer_fk foreign key(writer) references user(nick)
on delete cascade on update cascade,
constraint w_r_bno_fk foreign key(bno) references w_board(no)
on delete cascade
);


#######좋아요 관련 DDL#########
#랜선여행 게시판 좋아요 테이블(l_heart)
create table l_heart(
hno int auto_increment primary key,
bno int not null,
userid varchar(100) not null,
constraint l_heart_bno_fk foreign key(bno) references l_board(no)
on delete cascade,
constraint l_heart_userid_fk foreign key(userid) references user(nick)
on delete cascade on update cascade
);

#사진자랑 게시판 좋아요 테이블(p_heart)
create table p_heart(
hno int auto_increment primary key,
bno int not null,
userid varchar(100) not null,
constraint p_heart_bno_fk foreign key(bno) references p_board(no)
on delete cascade,
constraint p_haert_userid_fk foreign key(userid) references user(nick)
on delete cascade on update cascade
);

#쇼핑 게시판 좋아요 테이블(s_heart)
create table s_heart(
hno int auto_increment primary key,
bno int not null,
userid varchar(100) not null,
constraint s_heart_bno_fk foreign key(bno) references s_board(no)
on delete cascade,
constraint s_haert_userid_fk foreign key(userid) references user(nick)
on delete cascade on update cascade
);

#숙소 게시판 좋아요 테이블(a_heart)
create table a_heart(
hno int auto_increment primary key,
bno int not null,
userid varchar(100) not null,
constraint a_heart_bno_fk foreign key(bno) references a_board(no)
on delete cascade,
constraint a_haert_userid_fk foreign key(userid) references user(nick)
on delete cascade on update cascade
);


#######즐겨찾기 관련 DDL#########
#랜선여행 게시판 즐겨찾기 테이블 (l_favorite)
create table l_favorite(
fno int auto_increment primary key,
bno int not null,
userid varchar(100) not null,
constraint l_favorite_bno_fk foreign key(bno) references l_board(no)
on delete cascade,
constraint l_favorite_userid_fk foreign key(userid) references user(nick)
on delete cascade on update cascade
);

#사진자랑 게시판 즐겨찾기 테이블 (p_favorite)
create table p_favorite(
fno int auto_increment primary key,
bno int not null,
userid varchar(100) not null,
constraint p_favorite_bno_fk foreign key(bno) references p_board(no)
on delete cascade,
constraint p_favorite_userid_fk foreign key(userid) references user(nick)
on delete cascade on update cascade
);

#쇼핑 게시판 즐겨찾기 테이블 (s_favorite)
create table s_favorite(
fno int auto_increment primary key,
bno int not null,
userid varchar(100) not null,
constraint s_favorite_bno_fk foreign key(bno) references s_board(no)
on delete cascade,
constraint s_favorite_userid_fk foreign key(userid) references user(nick)
on delete cascade on update cascade
);

#숙소 게시판 즐겨찾기 테이블 (a_favorite)
create table a_favorite(
fno int auto_increment primary key,
bno int not null,
userid varchar(100) not null,
constraint a_favorite_bno_fk foreign key(bno) references a_board(no)
on delete cascade,
constraint a_favorite_userid_fk foreign key(userid) references user(nick)
on delete cascade on update cascade
);


#######메세지 관련 DDL#########
create table message(
no int auto_increment primary key,
room int not null,
send_nick varchar(50) not null,
recv_nick varchar(50) not null,
send_time datetime not null,
read_time datetime not null,
content varchar(1000) not null,
read_chk int not null,
constraint m_send_nick_fk foreign key(send_nick) references user(nick)
on delete cascade on update cascade,
constraint m_recv_nick_fk foreign key(recv_nick) references user(nick)
on delete cascade on update cascade
);

#######방문자 관련 DDL#########
CREATE TABLE visit (
date datetime primary key,
visit int(11) NOT NULL
);

#회원가입 유효성검사

더보기

회원가입창에서 하나라도 정해진 유효성에 맞지 않으면 회원가입 버튼이 비활성화된다.
모든 유효성 검사 통과시 회원가입 버튼은 활성화된다.

유효성 검사 실패시 유효성 검사 성공시

 

-코드 설명

 

각 입력창마다 유효성 정규식을 정의해주고 밑에 check라는 형식검사해주는 메소드를 하나 선언한다.

signupForm.jsp

 

ID를 예시로 들면 사용자의 입력값이 있을 경우 ajax를 통해서 컨트롤러에 정의된 usingId_chk.do로 user_id라는 데이터값을 GET방식으로 가지고 간다.

signupForm.jsp

 

아래 컨트롤러에서는 받아온 user_id를 to에 담아주고 이 to를 userDao의 loginLookup이란 메소드에 매개변수로 넣어주고 메소드를 호출한다. 
그리고 메소드 실행결과의 int값을 string으로 바꿔주어 "1"과 "0"의 경우로 나누어 result에 저장한다.
그리고 result를 반환해준다.

UserController.java

 

UserDAO.java에서는 컨트롤러에서 받은 to를 mapper에 정의된 login_lookup이란 select문과 매핑시켜서 실행한 뒤 sql결과를 받아온다.

UserDAO.java

mapper.xml에 sql문은 아래와 같이 정의되어 있다.

mapper.xml

 

다시 jsp페이지로 돌아와서 ajax가 성공했을 때의 경우를 작성한다.
컨트롤러에서 반환된 result값이 "0"이면 성공, "1"이면 실패인 경우로 나눠준다.

SingupForm.jsp

 

 

다른 입력값들도 위와 비슷하게 코드를 작성하면 된다. 


#비밀번호 암호화 및 복호화

더보기

-회원가입할 때 비밀번호 암호화

회원가입 jsp페이지에서 form태그를 이용해서 컨트롤러에 정의된 signup_ok.do로 각 데이터값을 가지고 넘어간다

SingupForm.jsp

 

userDao에 암호화 메소드를 호출하는데 매개변수로 jsp페이지에서 받은 유저가 입력한 비밀번호값을 넘겨준다.
그리고 암호화돼서 받은 encryPwd를 to에 담아준다.

UserController.java

 

ase256 라이브러리를 이용해서 암호화를 해준 뒤 암호화된 값을 반환해준다.

 

데이터베이스를 확인하면 아래와 같이 입력된다.

DB

 

 

 

-로그인할 때 비밀번호 복호화후 비교하기

 

loginForm.jsp에서 회원이 입력한 ID와 Password데이터를 컨트롤러에 넘겨준다.

 

아래 1에서 받아온 ID를 to에 담아주고 userDao의 loginDecry메소드를 호출해서 DB에서 해당 ID의 암호화된 비밀번호 데이터를 가져온다.


2에서는 DB에서 받아온 암호화된 비밀번호를 복호화함수로 복호화해준다.

 

3에서는 복호화된 비밀번호와 회원이 입력한 비밀번호가 일치하는지 검사한다.

UserController.java

 

먼저 1의 경우를 설명하면 아래 dao에서 login_decry란 sql 매퍼를 실행시킨다.

UserDao.java

 

login_decry sql은 아래와 같다. DB에서 to에 담겨져있는 유저 id값에 해당하는 암호화된 비밀번호를 반환해준다.

mapper.xml

 

다음으로 2의경우를 설명하면 DB에서 받아온 해당 유저의 암호화된 비밀번호를 복호화해준다.

아래는 복호화함수이다. 복호화함수에 암호화된비밀번호와 key값을 넣어주면 암호화된 비밀번호를 복호화해서
반환해준다.

 

마지막 3의 경우를 설명하면 실제 login.jsp페이지에서 유저가 입력한 비밀번호화 위에서 복호화한 비밀번호를 비교해주는 코드이다.

 


#로그인 기능 구현

더보기

회원정보가 없거나 비밀번호가 틀리면 아래와 같이 경고창이 뜬다.

회원정보 없을 시 비밀번호 틀렸을 시

 

컨트롤러에서 회원이 존재하고 비밀번호가 일치할 시 flag값을 0으로 설정한 뒤 다시 loginForm.jsp로 flag값을 넘겨준다.
만일 회원이 존재하지 않으면 flag는 2, 비밀번호가 틀렸을 시 flag는 1이 된다.

UserController.java

 

컨트롤러에서 받은 flag값을 loginForm.jsp페이지에서 JSP를 이용해서 경우를 아래와 같이 나눠준다.

loginForm.jsp

마지막으로 유저가 로그인하면 세션에 유저의 닉네임값이 저장되도록 하였다.
왜냐하면 게시판에서 글을 쓰거나 댓글을 작성할 때 모두 닉네임값이 들어가기 때문이다.


#쇼핑&숙소 게시판 제작
(CRUD, 좋아요기능, 댓글기능, 무한스크롤 기능, 검색 기능)

더보기

먼저 기본 구조는 아래와 같다.

출처: https://server-engineer.tistory.com/253

 

위 그림에서 MVC Model2 구조로 게시판 글쓰기, 글수정, 글삭제, 글목록을 구현하였다.
 

#CRUD

쇼핑게시판의 글쓰기 기능을 예시로 설명하려고 한다.

 

먼저 jsp페이지에서 form태그를 이용해서 컨트롤러로 유저가 작성한 글 내용과 사진파일명을 보내준다.

shopping_write.jsp

 

아래 사진 1에서처럼 컨트롤러에서 받아온 데이터를 to에 담아주고

2에서 DAO의 메소드를 호출한다. 이때 DAO의 메소드는 DB에 데이터를 insert해주는 기능이 있다. 

그래서 insert가 성공하면 flag값이 0, 실패하면 1이 반환된다.
이 flag값은 shopping_write_ok.jsp에서 글쓰기성공과 실패의 경우를 나눠준다.

ShoppingController.java

 

DAO에서 sql매퍼를 호출한다.

ShoppingDAO.java

 

mapper의 sql문은 아래와 같다.

mapper.xml

 

아래 shopping_write_ok.jsp에서 컨트롤러로부터 받은 flag값을 비교하여 글쓰기 성공여부를 나눈다.

shopping_write_ok.jsp

 

글수정과 글삭제도 위와 같은 구조를 가지고 sql문만 달라지게 된다.

 

 

글목록같은 경우 sql의 결과를 ArrayList로 받은 다음 jsp페이지에서 JSP반복문을 통해 페이지에 출력해준다.
아래 사진에서 1번이 sql결과를 가지고 있는 ArrayList를 컨트롤러로부터 받는 코드이고

2번이 JSP에서 StringBuffer안에다 html코드를 넣어준다.

shopping_list.jsp

 

위에서 StringBuffer에 저장된 html태그를 아래처럼 HTML body안에다 JSP표현식을 이용해서 출력시킨다.

shopping_list.jsp

 

소제목으로 이동

 

#좋아요기능

 

좋아요를 누르면 붉은 하트로 바뀌고 다시 누르면 빈 하트로 바뀐다.
ajax를 이용해서 ajax가 성공적으로 실행되면 jQuery를 이용해서 하트 모양을 바꿔주면 된다.

좋아요 누르기 전 좋아요 누른 후

 

-코드설명

 

아래 jsp페이지에서 1은 로그인상태에서 to.getHno()값이 null인 경우 빈하트가 나오게 하고,
null이 아닐 경우 꽉찬 하트가 나오게 하는 코드이다.
그리고 a태그에 해당 하트가 어떤 게시글의 하트인지를 표현하기 위해 idx로 게시글번호를 설정해준다.
또한 a태그 class에 heart-click을 써주어서 나중에 jQuery이벤트 정의할 때 이 class명을 사용한다.

shopping_list.jsp의 JSP부분

 

javascript안에 jQuery를 이용해서 위에서 만든 a태그의 heart-click이 클릭되었을 때의 이벤트를 정의해준다.
1은 a태그의 idx값을 no에 저장하고 ajax를 통해서 no 값을 shop_saveHeart.do 컨트롤러로 넘겨준다.

2는 ajax가 성공적으로 실행되면 좋아요 수를 갱신해주고 하트모양을 변경해주는 코드이다.

shopping_list.jsp

 

ajax를 통해서 들어오는 shop_saveHeart.do controller는 아래와 같다.

1에서 dao의 shopSaveHeart이란 메소드를 호출하는데 이때 게시물번호가 담겨져있는 to를 매개변수로 넘겨준다.

ShoppingController.java

 

아래는 Controller에서 호출한 ShoppingDAO의 shopSaveHeart메소드이다.

1에서 게시글번호 담겨져있는 to를 sto로 다시 선언해주고 해당 게시글의 좋아요 수를 1 더해주는 sql 매퍼를 실행시킨다.

2에서는 s_heart테이블에 어떤 게시글인지 나타내는 게시글번호와 어떤 유저가 좋아요를 눌렀는지 데이터를 insert시켜주는 매퍼이다.

3에서는 s_board테이블에서 해당 게시글의 좋아요 수를 가져온다.

ShoppingDAO.java

 

아래는 위에서 설명한 3개에 대한 sql매퍼이다.

 

또한 해당 게시글에 들어가서도 아래와 같이 좋아요를 누를 수 있다.

 

꽉찬하트를 빈하트로 바꾸는 과정은 위 과정과 같고 sql문만 달라진다.

 

 

해당 게시글의 좋아요 수 -1

좋아요 테이블에서 해당 게시글번호가 있는 로우 제거

게시판 테이블에서 해당 게시글 번호의 좋아요 수 가져오기

 

 

소제목으로 이동

 

#댓글기능(CRUD)

 

먼저 아래와 같이 게시글view페이지에서 댓글을 작성, 수정, 삭제, 목록 확인 가능하고,
답글 기능도 구현하였다.

댓글 쓰기

 

댓글 삭제의 경우 글쓴이인 경우와 아닌 경우로 나눠서 alert창이 뜨게된다.

댓글삭제

 

댓글 수정 또한 글쓴이인 경우와 아닌 경우로 나눠지며 아래와 같이 수정이 된다.

댓글 수정하기

 

답글쓰기기능은 아래와 같다.

답글쓰기

 

답글의 경우 답글삭제와 답글수정만 존재한다.

답글 삭제 및 수정

 

 

-코드 설명

 

먼저 각 게시판에 달리는 댓글목록기능을 예시로 살펴본다.

글목록 페이지에서 특정 게시글을 누르면 view.jsp 페이지로 이동하는 데 이때 컨트롤러에서 해당 글에 달린 댓글들을 ArrayList<to>형식으로 가져와서 JSP 스크립틀릿 안에서 StringBuffer 안에다가 댓글 html을 만들어준다.
아래와 같다.

Shopping_view.jsp

 

아래 사진은 Shopping_view.do  Controller이다.

1번에서 해당 게시글번호가 담겨져있는 commentTo를 ShoppingCommentDAO의 shopListComment메소드의 매개변수로 넘겨준다. 이 메소드의 반환값은 해당글에 달린 모든 댓글들을 담고 있는ArrayList<ShoppingCommentTO> 타입이다.

ShoppingController.java

 

아래 ShoppingCommentDAO에서 sql 을 실행시킨다.

ShoppingCommentDAO.java

 

where절에서 매개변수로 넘어온 게시글번호를 넣어준다.
또한 댓글을 작성한 유저의 프로필사진이 필요하므로 user 테이블과 s_reply 테이블을 inner join해준다.
또한 댓글들을 불러올 때 최신댓글은 가장 아래에 있고 답글은 부모 댓글 바로 아래에 있어야 하므로

order by절을 이용하여 가져오는 댓글들의 순서를 정해준다.

mapper.xml

grp는 댓글 그룹으로 부모댓글의 댓글번호이다.
grps는 특정 grp에서의 자식댓글 순서이다.
grpl은 부모댓글과 답글을 나눠주는 역할을 한다. 부모 댓글일 경우 0, 답글일 경우 1이다.
만약 답글의 답글(대대댓글)을 구현했으면 대댓글은 grpl이 2가 된다.

이해하기 쉽게 아래와 같이 댓글이 있다고 가정하자.

댓글 댓글번호 grp grps grpl
부모댓글1 1 1 2 0
부모댓글1의 답글1 2 1 1 1
부모댓글1의 답글2 3 1 0 1
부모댓글2 4 4 1 0
부모댓글2의 답글1 5 4 0 1

댓글의 작성, 수정, 삭제도 댓글목록기능과 비슷하게 구현했다.


댓글의 삭제를 예로 들면 게시글번호와 댓글 번호를 컨트롤러에 넘겨준다.
아래 코드에서 '댓글삭제'를 누르면 reply_deleteOK()' 자바스크립트 메소드가 호출한다.

Shopping_view.jsp

 

reply_deleteOK메소드는 아래와 같다.
1번에서 현재 유저와 댓글 글쓴이를 비교해서 다를 경우 alert창이 띄워지도록 한다.
맞을 경우 2번에서 댓글목록을 감싸고 있는 form태그의 action값을 보내질 컨트롤러 이름으로 변경하고
form 태그를 submit 시킨다.

ShoppingView.jsp

 

form태그는 아래와 같이 작성되어 있다.
이때 각 댓글마다 고유한 form태그가 필요하니깐 name값에 고유한 값인 댓글번호를 넣어준다.

그리고 컨트롤러로 보내질 각종 데이터값들을 input type="hidden"으로 보내준다.

ShoppingView.jsp

 

아래는 reply_deleteOk의 form태그가 보내지는 shopping_reply_deleteOK.do  Controller이다.

jsp페이지로부터 게시글번호, 댓글번호, 댓글그룹번호를 받은 뒤에 cto에 저장하고 ShoppingCommentDAO의 shopping_reply_deleteOk메소드를 호출한다. 이 메소드는 s_reply 테이블에서 해당 댓글을 지우는 기능을 하고 성공적으로 지워졌을 경우 flag 0값을 리턴해준다. 그리고 그 flag값을 가지고 shopping_reply_deleteOk.jsp 페이지로

이동한다.

ShoppingController.java

shopping_reply_deleteOk.jsp 페이지는 아래와 같이 JSP 스크립틀릿을 이용하여 flag값에 따라 댓글 삭제 성공 및 실패를 유저에게 표현해준다.

Shopping_reply_deleteOk.jsp

 

이제 ShoppingCommentDAO를 살펴보자.

먼저 1번에서는 현재 게시글번호와 댓글의 그룹번호가 같을 경우 부모댓글과 그 밑에 답글도 동시에 지우는 sql매퍼를 실행하고 다를 경우에는 답글만 삭제하는 sql매퍼를 실행한다.
성공적으로 댓글이 지워졌을 경우 flag값은 0이 되고 flag값을 리턴해준다.

ShoppingCommentDAO.java

 

매퍼는 아래와 같다.

mapper.xml

소제목으로 이동

 

 

다음으로 답글구현을 살펴본다.

 

아래와 같이 댓글목록을 만들어주는 JSP에서 grpl이 0인 경우, 즉 부모댓글인 경우에만 '답글쓰기'버튼이 만들어지도록 하였다.

또한 각 답글이 어떤 부모댓글의 답글인지를 나타내기 위해 idx에 부모댓글의 댓글번호를 입력하였다.
답글달기 버튼을 클릭하면 JavaScript에서 jQuery를 이용하여 클릭이벤트가 실행된다. 클릭이벤트가 실행되면

display none이었던 답글을 입력하는 창이 보이게되고 거기서 '답글등록'버튼을 누르면 onclick 메소드가 실행된다.

ShoppingView.jsp

 

아래와 같이 jQuery 클릭이벤트가 실행되고 css의 display속성이 block으로 바뀌어서 답글쓰는 textarea가 나오게 된다.

ShoppingView.jsp

 

'답글등록'버튼을 누르면 아래 jQuery이벤트가 호출된다. 

여기서는 컨트롤러로 보내질 form태그의 action속성이 보내질 컨트롤러의 이름으로 변경되고

submit시킨다.

ShoppingView.jsp

 

1번은 ShoppingView.jsp 페이지에서 가져오는 게시글번호, 댓글번호, 답글작성자, 답글내용 데이터를 commentTO에 담아주는 것이다.

2번은 부모댓글의 grp, grps, grpl을 가져오는 메소드를 호출하고 그 값을 다시 commentTO에 담아준 뒤 

자식댓글의 grps를 1씩 늘려준다.

3번은 댓글테이블에 해당답글을 insert시키는 메소드를 호출하고 성공시 flag값을 0으로 설정한다.

마지막으로 flag값을 shopping_rereply_ok.jsp로 보내준다.

ShoppingController.java

 

아래는 컨트롤러에서 호출하는 DAO메소드들이다.

ShoppingCommentDAO.java

 

아래는 DAO에서 호출하는 sql mapper들이다.

mapper.xml

 

마지막으로 컨트롤러로부터 shopping_rereply_ok.jsp페이지로 flag값을 가지고와서 아래와 같이 flag가 0일 때와

그렇지 않을 때를 나누어서 답글쓰기 성공, 실패를 나눈다.

shopping_rereply_ok.jsp

 

답글수정, 삭제도 위와 같은 방법으로 구현하였다.

 

소제목으로 이동

 

 

#무한스크롤 & 검색기능

 

페이지 번호가 있는 게시판으로 만들 수 있지만 스크롤을 내릴때마다 추가적으로 글을 불러오는 형식으로 게시판을 만들었다. 

 

아래처럼 스크롤이 가장 밑에 닿으면 jQuery를 이용하여 글을 불러오는 ajax를 실행한다.

 

아래와 같이 추가적인 글이 더 불러와진 것을 확인할 수 있다.

 

또한 검색기능은 아래와 같다.

 

 

 

 -코드 설명

 

1번은 무한스크롤로 불러올 페이지의 번호와 로딩바를 표현하기 위한 변수를 선언하는 부분이다.

2번은 스크롤이 바닥에 닿았는지 여부를 파악하기 위한 코드들이다. 이는 isBottom에 저장된다.

3번은 만약 isBottom이 true이면 즉, 스크롤이 바닥에 닿았다면 jQeury 함수를 종료시킨다. false이면 다음 코드들을 실행한다.

4번은 로딩바를 보이게하고 페이지 번호를 1 증가시킨다.

5번은ajax를 통해서 추가로 불러올 게시글을 가져오기 위해 컨트롤러로 이동시킨다.

이때 위에서 설정한 페이지번호와 검색어 데이터를 함께 넘겨준다.

그리고 ajax가 성공적으로 실행되면 불러온 게시글들을 card-list-contrainer라는 태그에 append시켜준다.

이때 불어온 게시글들은 shop_ajax_page.jsp 페이지 자체가 넘어오게 된다.(아래에서 설명)

6번은 불러온 게시글도 좋아요 기능이 되어야 하므로 위에서 선언했던 좋아요 이벤트들을 똑같이 복사 붙여넣기 해준다.

Shopping_list.jsp

 

이제 Controller부분을 살펴보자.

 

1번에서 불어올 게시글의 수를 지정한다.

2번에서 페이지번호가 넘어오는지 확인하고 넘어오면 넘어온 페이지 번호를 사용, 넘어오지 않으면 1을 사용한다.
  그리고 페이지번호를 이용해서 DB에서 몇 번재 로우에서 몇 번째 로우까지 가져올 것인지를 정하기 위해

  startRowNum과 endRowNum, rowCount를 선언해준다.

3번에서 검색어가 넘어왔는지 여부를 파악하고 넘어오지 않았다면 공백으로 선언한다.

ShoppingController.java

 

1번에서는 위에서 선언한 startRowNum과 endRowNum, rowCount를 sto에 담아준다.

2번에서는 검색어가 있는 경우 검색어도 sto에 담아준다.

3번에서는 게시글들을 불러오기 위해 DAO 메소드를 호출하는 부분인데 로그인된 경우와 비로그인 상태로 나눈다.

  왜냐하면 로그인된 경우 좋아요 누른 정보까지 가져와야하고 비로그인인 경우 단순히 게시글만 불러오면 되기

  때문이다.

ShoppingController.java

 

1번에서는 로그인된 상태의 게시글들을 불러온다.

2번에서는 DB에 저장된 총 게시글의 개수를 이용하여 전체 페이지 수를 구한다.

3번에서는 불어온 게시글이 담긴 ArrayList와 각종 데이터들을 shop_ajax_page.jsp로 보내준다.

ShoppingController.java

 

다음으로 DAO 메소드를 살펴본다.

 

1번은 비로그인 상태에서 게시글을 불러오는 sql mapper를 실행시키는 메소드

2번은 로그인 상태에서 게시글을 불러오는 sql mapper를 실행시키는 메소드

3번은 전체 페이지 수를 구하기 위해 전체 게시글 수를 불러오는 메소드이다.

ShoppingDAO.java

 

다음으로 mapper를 살펴본다.

 

먼저 비로그인 상태에서의 게시글 가져오는 sql문이다.
1번에서 게시글 작성자의 프로필 사진을 가져오기 위해 user테이블과 outer join을 한다.

2번에서 검색어가 있을 경우 where절로 검색어에 해당되는 로우들만 출력하기 위해 jstl로 조건문을 만들어준다.

3번에서 Controller에서 정의한 불러올 게시글의 시작점과 끝점을 이용해서 범위를 지정해준다.

mapper.xml

다음은 로그인된 상태에서의 sql문이다.
1번은 현재 로그인된 유저가 어떤 게시글의 좋아요를 눌렀는지까지 확인하기 위해 user테이블까지 outer join을 
  걸어준다.
2번과 3번은 앞서 설명한 것과 같다.

mapper.xml

마지막으로 총 글의 개수를 출력하는 sql이다.
이때 검색어가 있을 경우 검색어에 해당되는 글의 개수만 반환된다.

mapper.xml

 

다음으로 불러온 게시글들은 shop_ajax_page.jsp로 넘어가게 된다.

그런 뒤 shop_ajax_page.jsp페이지에서 게시글 html들을 만들게 된다.
아래와 같이 JSP 스크립틀릿 안에서 StringBuffuer안에 html태그를 모두 만들어주고 밑에서 표현식을 이용해서 출력한다. 
그러면 아까 shopping_list.jsp페이지에서 실행된 스크롤이벤트의 ajax의 반환값으로 shop_ajax_page.jsp 페이지 

자체가 반환된다.

shop_ajax_page.jsp

 

숙소 게시판도 쇼핑게시판과 모든 기능이 같기때문에 지금까지 설명한 방식대로 구현했다.

 

소제목으로 이동

 


#전세계 주요도시 날씨 데이터 크롤링

더보기

 

유럽 각 나라별 주요 도시 3군데의 현재시각기준 날씨 정보를 보여준다.

프랑스 날씨 정보
그리스 날씨 정보

 

-코드 설명

 

먼저 아래와 같이 쇼핑정보나 숙소정보 게시판을 누르면 국가선택 페이지가 나온다.

select_nation.jsp

 

'프랑스'를 클릭했다고 하면 shopping_list.jsp페이지의 JSP 스크립틀릿 부분에서 아래와 같이 국가에 해당하는

주요도시 3군데를 미리 citys라는 ArrayList에 선언해준다.

shopping_list.jsp

 

그리고 날씨정보를 가져올 수 있는 API를 사용하기 위해 미리 받아놓은 API KEY를 선언해준다.

shopping_list.jsp

 

날씨정보 API는 아래 사이트에서 나온 설명대로 따라하면 사용할 수 있다.
API이름은 OpenWeather 이다.
openweathermap.org/

 

Сurrent weather and forecast - OpenWeatherMap

Access current weather data for any location on Earth including over 200,000 cities! The data is frequently updated based on the global and local weather models, satellites, radars and a vast network of weather stations. how to obtain APIs (subscriptions w

openweathermap.org

위 API사이트에 회원가입하면 API KEY를 제공받을 수 있다.

이제 자바스크립트 부분에서 날씨정보를 가져오기 위해 ajax를 이용한다.

1번은 위에서 정의한 주요 도시 3군데를 자바스크립트 citys라는 배열에 다시 저장하는 것이다.

2번은 날씨정보 가져오는 API의 주소에 도시명과 API KEY를 데이터로 넘겨주어서 JSON형태로 날씨데이터를 반환

   받는다.

   이때 비동기식으로 ajax를 처리한다.

3번은 반환된 json데이터를 각 날씨에 맞게 나눠주는 코드이다. 예를들어 status가 clear sky이면 weather_img태그

   의 class를 wi wi-day-sunny로 설정하여 맑은 날씨의 이미지가 보이도록 한다.

shopping_list.jsp

이때 날씨 아이콘 부트스트랩을 사용하기 위해  아래 코드를 html head태그 안에 넣어준다.

 

 

 또한 추가적으로 출력할 날씨 데이터들을 아래와 같이 jQuery를 이용하여 html 태그에 넣어준다.

shopping_list.jsp

 

 


#내 프로필 구현

더보기

 

로그인한 뒤 마이페이지의 내 프로필을 누르면 아래와 같은 화면이 뜬다.

먼저 왼쪽에는 자신의 아이디와 프로필 사진, 개인 정보들이 보이고 '내 프로필 수정'버튼을 통해서 프로필 정보를

수정할 수 있다.

그리고 오른쪽에는 각 게시판마다 자신이 작성한 게시글들을 한꺼번에 볼 수 있으며 무한스크롤 기능이 포함되어 있다.

또한 실제 게시판에서는 각 게시글을 클릭하면 해당 게시글 페이지로 이동했는데 여기서는 modal창이 뜨도록 

구현하였다.

내 프로필

 

또한 게시글에 마우스 오버시 제목과 좋아요수, 댓글수가 나오도록 하였고 랜선여행하기 게시판의 경우
마우스 오버시 동영상도 재생된다.

 

 

-코드 설명

 

첫 번째는 유저 정보를 불러오는 코드에 대한 설명이다.

 

먼저 컨트롤러에서 profile.jsp로 보내질때 DAO와 mapper를 통해서 현재 로그인 중인 유저의 정보를 가져올 수 있도록 해놓았고 profile.jsp페이지에서는 jQuery를 이용하여 유저 정보를 아래처럼 출력할 수 있다.

profile.jsp

 

아래는 Controller이다.

1번에서는 현재 세션에 저장되어있는 닉네임값을 이용하여 유저정보를 가져오는 코드이다.

ProfileController.java

아래 DAO에서 mapper를 실행시킨다.

UserDAO.java

mapper에서 sql문을 실행시킨다.

mapper.xml

 

두 번째는 각 게시판별로 자신이 작성했던게시글들을 불러오는 코드에 대한 설명이다.

 

처음 내프로필 페이지에 들어오면 무조건 랜선여행 게시글들이 불러와지도록 한다.
그러기 위해서 아래처럼 GetList란 함수를 호출한다. 

profile.jsp

 

코드들이 길기 때문에 사진캡처가 어려워서 필요한 코드들을 아래에 적었다.

 

아래의 GetList함수는 무한스크롤을 위한 현재페이지, 게시글가져오기 위한 컨트롤러 이름, 좋아요기능을 위한 divName값이 매개변수로 필요하다.

 

<동일한 코드 내에서 여러 개의 컨트롤러로 데이터를 보내야 되는 경우>

divName이란 변수로 보내져야되는 각 게시판 이름을 정의한다.

예를들어 랜선여행이면 lantrip, 사진자랑하기 게시판이면 picture 등이 된다.

 

이 divName 을 매개변수로 주는 이유는 GetList 메소드 안에서 호출되는 다른 메소드들이 ajax를 통해서 필요한 컨트롤러로 보내져야되기 때문이다.

 

만약에 랜선여행하기 게시판에 댓글을 쓰는 경우 보내져야되는 컨트롤러 이름은 lantrip_write_reply.do이고

사진자랑하기 게시판에 댓글을 쓰는 경우 보내져야되는 컨트롤러 이름은 picture_write_reply.do이다

따라서 ajax의 url에  "divName_write_reply.do" 와 같이 작성해주면 해당 컨트롤러로 보내지게 된다.

그렇기 때문에 divName을 각 게시판 이름에 맞게 선언을 해주어야 한다.


이제 아래 코드를 살펴보자.

게시글 불러오기 ajax부분을 통해 특정 게시판의 게시글들만 불러오도록 하고 

게시글을 불러왔을 때는 다른 게시판의 div태그는 모두 jQeury로 empty 시켜준다.

왜냐하면 각 게시판의 div태그를 비우지않고 그대로 놔둘경우 나중에 modal창이 중복돼서 안 열리는 경우가 발생한다.


또한 게시글 불러왔을 때 좋아요 기능과 댓글쓰기, 삭제, 수정, 답글쓰기, 삭제 기능들이 돼야 하므로

모두 함수로 선언해준뒤 GetList 함수안에서 호출시킨다.

   const GetList = function( currentPage, doName, divName ){
      
      //하트 컨트롤러 이름 만들어주기
      let heartUrl = 'lanTrip_';
      if( divName == 'lantrip' ) {
         
      } else if( divName == 'Picture' ) {
         heartUrl = '';
         
      } else if( divName == 'shop' ) {
         heartUrl = 'shop_';
         
      } else if( divName == 'accom' ) {
         heartUrl = 'accom_';
      }
      
      //게시글 불러오기
      $.ajax({
         url: doName,
         method:"GET",
         data:"pageNum="+currentPage,
         //ajax_page.jsp의 내용이 jspPage로 들어온다.
         success:function( jspPage ){
            //응답된 문자열은 jsp 형식이다.(profile/게시판명_ajax_page.jsp에 응답내용이 있다.)
            //해당 문자열을 특정div 태그에 붙여준다.
            
            if( divName == 'lantrip' ) {
               $('#Picture' ).empty();
               $('#shop' ).empty();
               $('#accom' ).empty();
               
            } else if( divName == 'Picture' ) {
               $('#lantrip' ).empty();
               $('#shop' ).empty();
               $('#accom' ).empty();
               
            } else if( divName == 'shop' ) {
               $('#lantrip' ).empty();
               $('#Picture' ).empty();
               $('#accom' ).empty();
               
            } else if( divName == 'accom' ) {
               $('#lantrip' ).empty();
               $('#Picture' ).empty();
               $('#shop' ).empty();

            }

            $( '#'+divName ).append(jspPage);

            
            //로딩바를 숨긴다.
            $(".back-drop").hide();
            //로딩중이 아니라고 표시한다.
            isLoading=false;
            
            if( divName == 'lantrip' ) {
               $('.card-img-top').mouseover(function(){
                   $(this).get(0).play();
               }).mouseout(function(){
                   $(this).get(0).pause();
               });
            }
            
            
            
            // 로그인 한 상태에서 하트를 클릭했을 때 (로그인한 상태인 하트의 <a></a> class명: heart-click)
            $(".heart-click").unbind('click');
            $(".heart-click").click(function() {
               
               // 게시물 번호(no)를 idx로 전달받아 저장합니다.
               let no = $(this).attr('idx');
               
               // 빈하트를 눌렀을때
               if($(this).children('svg').attr('class') == "bi bi-suit-heart"){
                  
                  $.ajax({
                     url : heartUrl+'saveHeart.do',
                     type : 'get',
                     data : {
                        no : no,
                     },
                     success : function(to) {
                        //페이지 새로고침
                        //document.location.reload(true);
                        
                        let heart = to.heart;
                        
                        // 페이지, 모달창에 하트수 갱신
                        $('#m_heart'+no).text(heart);
                        $( '.span_heart'+no ).text(heart);
                        
                        alert("하트추가 성공");
                     },
                     error : function() {
                        alert('서버 에러');
                     }
                  });
                  console.log("꽉찬하트로 바껴라!");
                  
                  // 꽉찬하트로 바꾸기
                  $(this).html("<svg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='currentColor' class='bi bi-suit-heart-fill' viewBox='0 0 16 16'><path d='M4 1c2.21 0 4 1.755 4 3.92C8 2.755 9.79 1 12 1s4 1.755 4 3.92c0 3.263-3.234 4.414-7.608 9.608a.513.513 0 0 1-.784 0C3.234 9.334 0 8.183 0 4.92 0 2.755 1.79 1 4 1z'/></svg>");
                  $('.heart_icon'+no).html("<svg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='currentColor' class='bi bi-suit-heart-fill' viewBox='0 0 16 16'><path d='M4 1c2.21 0 4 1.755 4 3.92C8 2.755 9.79 1 12 1s4 1.755 4 3.92c0 3.263-3.234 4.414-7.608 9.608a.513.513 0 0 1-.784 0C3.234 9.334 0 8.183 0 4.92 0 2.755 1.79 1 4 1z'/></svg>");
                  
               // 꽉찬 하트를 눌렀을 때
               } else if($(this).children('svg').attr('class') == "bi bi-suit-heart-fill"){
                  console.log("꽉찬하트 클릭" + no);
                  
                  $.ajax({
                     url : heartUrl+'removeHeart.do',
                     type : 'get',
                     data : {
                        no : no,
                     },
                     success : function(to) {
                        //페이지 새로고침
                        //document.location.reload(true);
                        
                        let heart = to.heart;
                        // 페이지, 모달창에 하트수 갱신
                        $('#m_heart'+no).text(heart);
                        $( '.span_heart'+no ).text(heart);
                        
                        alert("하트삭제 성공");
                     },
                     error : function() {
                        alert('서버 에러');
                     }
                  });
                  console.log("빈하트로 바껴라!");
                  
                  // 빈하트로 바꾸기
                  $(this).html('<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-suit-heart" viewBox="0 0 16 16"><path d="M8 6.236l-.894-1.789c-.222-.443-.607-1.08-1.152-1.595C5.418 2.345 4.776 2 4 2 2.324 2 1 3.326 1 4.92c0 1.211.554 2.066 1.868 3.37.337.334.721.695 1.146 1.093C5.122 10.423 6.5 11.717 8 13.447c1.5-1.73 2.878-3.024 3.986-4.064.425-.398.81-.76 1.146-1.093C14.446 6.986 15 6.131 15 4.92 15 3.326 13.676 2 12 2c-.777 0-1.418.345-1.954.852-.545.515-.93 1.152-1.152 1.595L8 6.236zm.392 8.292a.513.513 0 0 1-.784 0c-1.601-1.902-3.05-3.262-4.243-4.381C1.3 8.208 0 6.989 0 4.92 0 2.755 1.79 1 4 1c1.6 0 2.719 1.05 3.404 2.008.26.365.458.716.596.992a7.55 7.55 0 0 1 .596-.992C9.281 2.049 10.4 1 12 1c2.21 0 4 1.755 4 3.92 0 2.069-1.3 3.288-3.365 5.227-1.193 1.12-2.642 2.48-4.243 4.38z" /></svg>');
                  $('.heart_icon'+no).html('<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-suit-heart" viewBox="0 0 16 16"><path d="M8 6.236l-.894-1.789c-.222-.443-.607-1.08-1.152-1.595C5.418 2.345 4.776 2 4 2 2.324 2 1 3.326 1 4.92c0 1.211.554 2.066 1.868 3.37.337.334.721.695 1.146 1.093C5.122 10.423 6.5 11.717 8 13.447c1.5-1.73 2.878-3.024 3.986-4.064.425-.398.81-.76 1.146-1.093C14.446 6.986 15 6.131 15 4.92 15 3.326 13.676 2 12 2c-.777 0-1.418.345-1.954.852-.545.515-.93 1.152-1.152 1.595L8 6.236zm.392 8.292a.513.513 0 0 1-.784 0c-1.601-1.902-3.05-3.262-4.243-4.381C1.3 8.208 0 6.989 0 4.92 0 2.755 1.79 1 4 1c1.6 0 2.719 1.05 3.404 2.008.26.365.458.716.596.992a7.55 7.55 0 0 1 .596-.992C9.281 2.049 10.4 1 12 1c2.21 0 4 1.755 4 3.92 0 2.069-1.3 3.288-3.365 5.227-1.193 1.12-2.642 2.48-4.243 4.38z" /></svg>');
               }
            });
            
            
            // 로그인 안한 상태에서 하트를 클릭하면 로그인해야한다는 알림창이 뜹니다.
            // (로그인한 상태인 하트의 <a></a> class명: heart-notlogin)
            $(".heart-notlogin").unbind('click');
            $(".heart-notlogin ").click(function() {
               alert('로그인 하셔야 하트를 누를수 있습니다!');
            });
            
            
            // 댓글아이콘을 클릭했을때 댓글 리스트 함수를 호출
            $(".open_reply_list").unbind('click');
            $(".open_reply_list").click(function(){
               let no = $(this).attr('idx');
               // 게시물에 no에 해당하는 댓글 리스트를 가져오는 함수   
               ReplyList( no, divName );
            });
            
            // 댓글 달기 버튼 클릭시  실행
            $(".write_reply").unbind('click');
            $(".write_reply").click(function(){
               // 게시물 번호
               let no = $(this).attr('idx');
               
               // 댓글 입력란의 내용을 가져온다.
               let content = $("#input_reply" + no).val();
               
               // 앞뒤 공백을 제거한다.(띄어쓰기만 입력했을때 댓글작성안되게 처리하기위함)
               content = content.trim();
               
               if(content == ""){   // 입력된게 없을때
                  alert("글을 입력하세요!");
               }else{   
                  // 입력란 비우기
                  $("#input_reply" + no).val("");
                  
                  // reply+1 하고 그 값을 가져옴
                  $.ajax({
                     url : divName+'_write_reply.do',
                     type : 'get',
                     data : {
                        bno : no,
                        content: content,
                        key: 'profile'
                     },
                     success : function(to) {
                        let reply = to.reply;
                        // 페이지, 모달창에 댓글수 갱신
                        $('#m_reply'+no).text(reply);
                        $('span_replyy'+no).text(reply);
                        
                        alert("댓글 작성 성공");
                        
                        // 댓글리스트를 새로 받아오기
                        ReplyList(no, divName);
                     },
                     error : function() {
                        alert('서버 에러');
                     }
                  });
                  
               }
               
            });
         }
         
      });
   }

 

댓글 목록 불러오는 메소드이다.
이 메소드 안에는 ajax를 통해서 해당 게시글에 댓글목록들을 모두 불러오고 ajax 성공메소드 안에 답글 작성 이벤트, 삭제이벤트를 포함시켜준다.

   //============= 댓글 리스트 가져오는 함수 =============
   const ReplyList = function( no, divName ) {
      console.log( 'replyllist의 divName확인중 : ' + divName );
      $.ajax({
         url : divName+'_replyList.do',
         type : 'get',
         data : {
            no : no
         },
         success : function( jspPage ) {
            ///////////// 동적으로 넣어준 html에 대한 이벤트 처리는 같은 함수내에서 다 해줘야한다.
            ///////////// $(document).ready(function(){}); 안에 써주면 안된다.
            
            // 댓글 리스트 부분에 받아온 댓글 리스트를 넣기
            $(".reply-list"+no).html( jspPage );
            
            // 답글에서 답글달기를 누르면 input란에 "@답글작성자"가 들어간다.
            //$('.write_re_reply_start').on('click', function(){
            //   $('#input_rereply'+ $(this).attr('no')).val("@"+$(this).attr('writer')+" ");
            //});
            

            //답글을 작성한 후 답글달기 버튼을 눌렀을 때 그 click event를 아래처럼 jquery로 처리한다.
            $('button.btn.btn-success.mb-1.write_rereply').on( 'click', function() {
               console.log( 'no', $(this).attr('no') );
               console.log( 'bno', $(this).attr('bno') );
               let bno = $(this).attr('bno');
               let no = $(this).attr('no');

               // 답글을 DB에 저장하는 함수를 호출한다. bno와 no를 같이 넘겨주어야한다.
               WriteReReply( bno, no, divName );
            });
            
            // 삭제버튼을 클릭했을 때
            $('.reply_delete').on('click', function(){
               // 모댓글 삭제일때
               if( $(this).attr('grpl') == 0 ){   
                  DeleteReply( $(this).attr('no'), $(this).attr('bno'), $(this).attr('grpl'), divName );

               // 답글 삭제일때
               }else{
                  DeleteReReply( $(this).attr('no'), $(this).attr('bno'), $(this).attr('grp'), divName );
               }
               
            })

         },
         error : function() {
            alert('서버 에러');
         }
      });
   };

 

답글달기 메소드이고, 마찬가지로 ajax로 각 게시판에 해당하는 컨트롤러로 게시글번호, 댓글번호, 답글내용 데이터를 전송시켜주고 답글이 달린 이후의 총 댓글수를 반환시켜준다.
반환된 댓글 수는 내프로필 게시판 목록의 댓글수와 modal창 안에서의 댓글수를 다시 갱신시켜준다.

그리고나서 다시 댓글목록 메소드를 호출해서 댓글목록을 받아온다.

   // 답글 달기 버튼 클릭시  실행 - 답글 저장, 댓글 갯수 가져오기
   const WriteReReply = function( bno, no, divName ) {      
      // 댓글 입력란의 내용을 가져온다. 
      // ||"" 를 붙인 이유  => 앞뒤 공백을 제거한다.(띄어쓰기만 입력했을때 댓글작성안되게 처리하기위함)
      let content = $("#input_rereply" + no).val();
      content = content.trim();
      
      if(content == ""){   // 입력된게 없을때
         alert("글을 입력하세요!");
      }else{   
         // 입력란 비우기
         $("#input_rereply" + no).val("");
         
         // reply+1 하고 그 값을 가져옴
         $.ajax({
            url : divName+'_write_rereply.do',
            type : 'get',
            data : {
               no : no,
               bno : bno,
               content: content
            },
            success : function(to) {
               
               let reply = to.reply;
               // 페이지, 모달창에 댓글수 갱신
               $('#m_reply'+no).text(reply);
               $('.span_reply'+no).text(reply);
               
               alert("답글 작성 성공");
               
               // 게시물 번호(bno)에 해당하는 댓글리스트를 새로 받아오기
               ReplyList( bno, divName );
            },
            error : function() {
               alert('서버 에러');
            }
         });
         
      };
   };

 

부모댓글 삭제 메소드이다.

마찬가지로 ajax를 이용하여 해당되는 게시판 컨트롤러로 댓글번호, 게시글번호, 댓글깊이 데이터를 보내주고 새로 갱신된 댓글수를 반환받아서 게시판목록과 modal내부의 댓글수를 갱신시킨다.

그리고 삭제된 이후의 댓글목록을 다시 받아온다.

   // 모댓글 삭제일때
   const DeleteReply = function( no, bno, grpl, divName ){
      // grp이 no인 댓글이 있는 경우 content에 null을 넣고 없으면 삭제한다.
      $.ajax({
         url : divName+'_delete_reply.do',
         type : 'get',
         data : {
            no : no,
            bno : bno,
            grpl : grpl
         },
         success : function(to) {
            
            let reply = to.reply;
            
            console.log( "모댓글 reply : " + reply );
            
            // 페이지, 모달창에 댓글수 갱신
            $('#m_reply'+bno).text(reply);
            $('.span_reply'+bno).text(reply);
            
            alert("모댓글 삭제 성공");
            
            // 게시물 번호(bno)에 해당하는 댓글리스트를 새로 받아오기
            ReplyList( bno, divName );
         },
         error : function() {
            alert('서버 에러');
         }
      });
   };

 

답글 삭제 메소드이다.

마찬가지로 ajax를 이용하여 해당 컨트롤러로 댓글번호, 게시글번호, 댓글그룹 데이터를 보내주고

댓글수를 반환받아서 댓글수를 갱신시킨다.

그리고 새롭게 댓글목록을 불러온다.

   //답글 삭제일때
   const DeleteReReply = function( no, bno, grp, divName ){
      
      //console.log("grp : " + grp);
      
      // 답글을 삭제한다.
      $.ajax({
         url : divName+'_delete_rereply.do',
         type : 'get',
         data : {
            no : no,
            bno : bno,
            grp : grp
         },
         success : function(to) {
            
            let reply = to.reply;
            
            console.log( "자식댓글 reply : " + reply );
            
            // 페이지, 모달창에 댓글수 갱신
            $('#m_reply'+bno).text(reply);
            $('.span_reply'+bno).text(reply);
            
            alert("답글 삭제 성공");
            
            // 게시물 번호(bno)에 해당하는 댓글리스트를 새로 받아오기
            ReplyList( bno, divName );
         },
         error : function() {
            alert('서버 에러');
         }
      });
      
   };

 

게시글 삭제메소드이다.

ajax를 이용하여 해당되는 게시판의 컨트롤러로 게시글번호 데이터를 넘겨주고

게시글을 삭제시킨다.

   //게시글 삭제하기
   const BoardDelete = function( no, divName ){
      //alert("함수들어왔다!");
      
      $.ajax({
         url : divName+'_delete_ok.do',
         type : 'get',
         data : {
            no : no,
         },
         success : function(to) {
            
            document.location.reload();
            alert("삭제되었습니다!");
            
            
         },
         error : function() {
            alert('서버 에러');
         }
      });
   }

 

1. 먼저 GetList메소드에서 ajax로 각 게시판별 게시글을 불러오는 방법 및 무한스크롤이 되는 방법을 살펴본다.

아래는 GetList메소드 안에 ajax 부분이다.

위 그림 1번에서 아래 빨간색 네모 부분의 html들을 모두 지워준다.

그리고 ajax를 통해 반환된 데이터는 profile_lanTrip_ajax_page.jsp페이지인데 아래와 같다.
jstl을 이용하여 html태그들을 모두 만들어준다.

profile_lanTrip_ajax_page.jsp

그리고 2번에서 반환된 jsp페이지를 append 시켜준다.
이때 append되는 html태그는 아래와 같다.

 

아래는 컨트롤러이다.

무한스크롤 설명할 때의 컨트롤러와 똑같다. dao랑 mapper도 똑같다.

ProfileController.java

 

2. modal창에서의 기능 설명

중복되는 기능이 있으므로 랜선여행하기 modal창에서의 댓글달기 기능만 설명하려고 한다.

 

먼저 modal창은 GetList 메소드의 ajax로 만들어지는 profile_lanTrip_ajax_page.jsp에서 만들어진다.
아래가 modal창 html에서 댓글관련 html부분이다.

profile_lanTrip_ajax_page.jsp

1번은 위에서 설명한 댓글목록을 불러오는 메소드인 ReplyList의 반환값이 append되는 div이다.
2번은 댓글을 작성하는 곳이고 '댓글 달기'버튼을 누르면 GetList안에있는 write_reply이벤트가 실행된다.
write_reply 이벤트는 아래와 같다.

profile.jsp

 write_reply이란 버튼 클릭시 ajax를 통해 lantrip_write_reply.do로 게시글번호, 댓글내용, key값으로 profile을 넘겨준다.
그리고 ajax성공시 반환되는 값은 to 타입이고 to의 갱신된 댓글수 데이터가 포함되어 있다.
갱신된 댓글수 데이터를 이용해 게시판목록과 modal창의 댓글수를 갱신시킨다.
그리고 마지막으로 다시 댓글목록을 불러온다.

다음은 lantrip_write_reply.do부분의 컨트롤러이다.

ProfileController.java

1번에서는 ajax로 넘어오는 각 데이터값들을 to에 담아준 뒤 lantripReplyDAO의 lantripWriteReply이란 메소드를 실행시켜준다. 
이 메소드는 l_reply테이블에 해당 댓글을 insert 해주는 sql을 실행시키는 기능과 총댓글수를 sql을 통해서 구한다음 to타입으로 반환시켜준다.

아래는 LanTripReplyDAO과 mapper이다. 이는 댓글구현에서 설명한 코드와 같다.

LanTripReplyDAO.java
mapper.xml

 댓글 삭제, 답글쓰기, 삭제도 위와 같은 과정으로 구현하였다.


#좋아요 목록 구현

더보기

각 게시판에서 내가 좋아요 누른 게시글들을 한꺼번에 볼 수 있는 게시판이다.
위에 게시판 탭에서 해당 게시판을 누르면 그 게시판에서 내가 좋아요 누른 게시글들이 아래 보이게된다.
여기서도 무한스크롤과 좋아요 기능이 적용된다.
이때 좋아요 기능은 빈하트로 바뀐다음 페이지가 새로고침되면 좋아요를 푼 게시글은 안보이게 된다.

아래는 실제 게시판 모습이다.

여기서는 좋아요 누른 게시글만 가져오는 기능을 소개하려고 한다.

-코드 설명

 

해당 코드는 기존 무한스크롤과 좋아요 코드와 모두 같고 mapper의 sql만 다르기 때문에 sql만 설명하려고 한다.

	<!-- 랜선여행하기 글리스트 가져오는 sql -->
	<select id="lantrip_favoriteList" parameterType="com.exam.model1.lantrip.LanTripTO" resultType="com.exam.model1.lantrip.LanTripTO">
		select b.no, b.subject, b.content, b.writer, date_format(b.wdate, '%Y-%m-%d') wdate, b.hit, b.location, b.video, b.reply, b.heart, h.hno, u.profile
		from l_board b inner join l_heart h
		on b.no = h.bno
		inner join user u
		on b.writer = u.nick
		where h.userid like #{ nick }
		order by b.no desc
		limit #{startRowNum}, 8
	</select>

일단 기존에 게시글목록을 가져올 때는 user테이블과 outer join을 사용해서 좋아요가 안눌린 게시글까지 가져왔는데 여기서는 inner join을 통해 heart테이블의 게시글번호가 있는 게시글 중에서 글쓴이가 현재 로그인된 유저 닉네임과 같은 게시글만 불러오는 형식으로 구현하였다.

 

즉 쉽게 정리하면 다음과 같다.

1. heart테이블 게시글 번호 = 게시글 번호 

2. 1.에서 구한 게시글의 글쓴이 = 현재 로그인 중인 유저 닉네임

 


#클라우드 서버 웹호스팅

더보기

웹호스팅을 위해 네이버 클라우드를 이용했고 예전에 정리한 글을 참고하면된다.
아래 글에서 2, 3, 4번을 그대로 따라하면 된다.
2, 3번은 서버 생성하고 공인IP받는 과정이고, 4번은 MariaDB 설치하고 한글설정 및 외부접속 허용하게 포트를 개방하는 과정이 포함되어있다.

earthconquest.tistory.com/145?category=897563

 

 

이제 서버 생성 및 공인IP 제공 그리고 DB와 FTP, Apache-tomcat까지 설치된 상태라면 아래과정을 진행하면된다.
특히 DB안에 미리 window에서 쓰던 database와 table명을 똑같이해서 만들어줘야하고
프로젝트에서 root-context의 DB접속하기 위한 ID와 PW, Port번호도 윈도우에 쓰던 MariaDB와 일치하게 만들어줘야 한다.


먼저 프로젝트에 쓰이는 모든 윈도우 경로를 모두 리눅스 경로로 바꿔주어야 한다.

예시로 아래와 같이 리눅스 실제 경로를 확인하고 아래와 같이 수정해준다.

 

그리고 프로젝트를 war파일로 만들어준다. 그리고 아래처럼 리눅스 서버 apache-tomcat/webapp 디렉터리 안에 해당 war파일을 넣어준다. 그리고 기다리면 자동으로 압축이 풀어진다.
만일 압축이 풀리지 않으면 war파일 지웠다가 톰캣을 시작하고 다시 넣어보면 된다.

그리고 아래 명령어로 아파치 톰캣을 실행시킨다.

[want@want ~]$ ./apache-tomcat-9.0.43/bin/catalina.sh run

 

그리고 웹 주소창에 공인IP뒤에 프로젝트 디렉터리명/시작페이지.do를 적어주면 성공적으로 웹호스팅이 된 것을 확인할 수 있을 것이다.

http://115.85.180.10:8080/Want/home.do

 

 

 

 

 

 

 


4. 기타

팀원 블로그
해선 : gimmesome.tistory.com/

혁준 : juuunew.tistory.com/

현수 : kkangsman.tistory.com/

 

728x90
반응형
Comments