바트심슨으로 하루만 살고 싶다

[스프링 프레임워크]게시판 만들기 #6 : 글 작성, 게시글 목록, 게시글 상세보기, 게시글 수정-삭제 접근 방지 본문

Java/Spring

[스프링 프레임워크]게시판 만들기 #6 : 글 작성, 게시글 목록, 게시글 상세보기, 게시글 수정-삭제 접근 방지

바트심슨바게트 2023. 2. 16. 23:19

*이전 내용

[스프링 프레임워크]게시판 만들기 #5 : 로그인, 로그아웃, 정보수정

 

[스프링 프레임워크]게시판 만들기 #5 : 로그인, 로그아웃, 정보수정

* 이전 내용 [스프링 프레임워크]게시판 만들기 #4 : 회원가입 [스프링 프레임워크]게시판 만들기 #3 : 데이터베이스 테이블 생성 및 게시판 카테고리 출력, 페 * 이전 내용 [스프링 프레임워크]게

11027bart.tistory.com


글 작성

카테고리별 main 페이지 폼은 아래 사진과 같고, 글쓰기 클릭시 글작성 폼으로 넘어가게 된다.

글 작성의 기본 폼은 아래 사진과 같다.
제목과 내용을 입력받아 해당 게시글의 정보를 DB에 저장한다.

고려해야할 사항

1. 게시글 작성 후 수정, 삭제는 본인만 가능하도록 만들기
2. 게시글 저장 이후 

write.jsp

<form:form action="${root }board/write_pro" method="post" modelAttribute="writeBean">
    <form:hidden path="content_board_idx"/>
	<div class="form-group">
		<form:label path="content_subject">제목</form:label>
		<form:input path="content_subject" class="form-control"/>
		<form:errors path="content_subject" style='color:red'/>
	</div>
	<div class="form-group">
		<form:label path="content_text">내용</form:label>
		<form:textarea path="content_text" class="form-control" rows="10" style="resize:none"/>
		<form:errors path="content_text" style='color:red'/>
	</div>
	<div class="form-group">
		<div class="text-right">
			<form:button type="submit" class="btn btn-primary">작성하기</form:button>
		</div>
	</div>
</form:form>

게시글 작성 폼에서 입력한 데이터를 modelAttriute로 "writeBean"을 지정해 해당 이름을 가진 Bean에서 데이터를 받을 수 있도록 한다.

여기서 <form:hidden> 태그를 이용해 content_board_idx의 값을 write_pro로 넘겨준다.

그리고 두 텍스트는 모두 입력이 되어야지만 작성이 가능하도록 유효검 검사를 지정한 후 error.peoperties에 에러메시지를 작성한다.


Content DTO

package co.ky.sol.beans;

public class Content {
	
	private int content_idx;	//게시글 번호
	
	@NotBlank
	private String content_subject;	//게시글 제목
	
	@NotBlank
	private String content_text;	//게시글 본문
	
	private int content_writer_idx;	//작성자 회원번호
	private int content_board_idx;	//카테고리 번호
	private String content_date;	//작성일
    
// Getter, Setter 생략

}

글 제목이 되는 content_subject와 글 본문이 되는 content_text를 @NotBlank로 지정해 빈칸이 될 수 없도록 만든다.


errors.properties

@NotBlank 어노테이션의 에러메시지 지정

NotBlank.writeBean.content_subject = 반드시 제목을 입력해 주세요.
NotBlank.writeBean.content_text = 반드시 내용을 입력해 주세요.

BoardMapper

글 작성이 완료되면, 데이터 베이스에 추가되어야 하기 때문에 insert 쿼리문을 작성해 준다.
로그인 한 회원이 작성한 게시글의 정보를 content_table에 저장한다. ( 회원 번호는 로그인 시 세션에 저장되어있다 )

package co.ky.sol.mapper;

import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Select;

import co.ky.sol.beans.Content;

public interface BoardMapper {

	@Insert("insert into content_table (content_idx, content_subject, content_text, "
			+ "content_writer_idx, content_board_idx, content_date) values "
			+ "(content_seq.nextval, #{content_subject}, #{content_text}, #{content_writer_idx}, #{content_board_idx}, SYSDATE)")
	void addContent(Content writeBean);
}

BoardDao

package co.ky.sol.dao;

@Repository
public class BoardDao {

	@Autowired
	private BoardMapper boardMapper;
	
	public void addContent(Content writeBean) {
		
		boardMapper.addContent(writeBean);
	}
}

BoardMappe를 자동 주입받아 Mapper의 쿼리문을 다시 재정의 한다.


BoardService

세션에 저장된 회원 번호를 작성자 번호에 주입해 폼에서 입력한 데이터가 쿼리문 실행 시 같이 주입되도록 한다.

package co.ky.sol.service;

@Service
public class BoardService {
	
	@Autowired
	private BoardDao boardDao;

	@Resource(name="loginBean")
	private User loginBean;
	
	public void addContent(Content writeBean) {
		
		writeBean.setContent_writer_idx(loginBean.getUser_idx());
		boardDao.addContent(writeBean);
	}
}

BoardController

해당 게시물이 어느 카테고리에서 작성되었는지를 알기 위해 @RequestParam으로 카테고리 번호를 가져와 
게시글 객체인 writeBean에 content_board_idx 값을 설정해준다.

@Valid와 @ModelAttribute로 요청시 넘어온 Content 객체를 지정해 유효성 검사를 진행한다.
만약 유효성 검사에서 아무 문제가 발생하지 않았다면 DB에 게시글이 저장되며, write_success 페이지로 넘어가 아래 사진과 같은 팝업창 호출 후 확인을 누르면 게시글 상세정보 페이지로 넘어간다.

글 작성 완료시

write_success.jsp

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<c:set var='root' value="${pageContext.request.contextPath }/" />
<script>
	alert('저장되었습니다.')
	location.href = '${root}board/read?board_info_idx=${writeBean.content_board_idx}&content_idx=${writeBean.content_idx}'
</script>

파라미터로 게시판 카테고리 번호 ( writeBean.content_board_idx ) 와 게시글 번호 ( writeBean.content_idx ) 가 함께 방금 작성한 게시글 상세페이지로 넘겨준다.이때 넘어가는 게시글 번호와 카테고리 번호는 게시글 상세보기 페이지 작업 시 사용된다.


게시글 테이블 저장 정보

 


게시글 목록

상단의 카테고리 이름을 클릭하는 경우 해당 카테고리의 게시글 목록을 띄우기 위한 작업을 해주어야 한다.

우선 이전에 작성한 topmenu.jsp의 코드에서 카테고리 이름을 <a>태그를 이용해 생성했다.

<ul class="navbar-nav">
	<c:forEach var='obj' items='${topmenuList }'>
		<li class="nav-item">
			<a href="${root }board/main" class="nav-link">${obj.board_info_name }</a>
		</li>
	</c:forEach>
</ul>

href를 통해 지정된 url에 내가 이동하고자 하는 카테고리로 이동하기 위해서는 이동하고자 하는 카테고리의 번호를 

파라미터에 담아 요청을 보내주어야 한다.

그리고 매핑 과정에서 해당 파라미터를 이용해 DB에서 카테고리별로 게시글을 가지고와서 저장하는 작업을 해주어야한다.


BoardController

@Autowired
private BoardService boardService;

//게시글 목록 페이지
@GetMapping("/main")
public String main(@RequestParam("board_info_idx") int board_info_idx, Model mo) {

	mo.addAttribute("board_info_idx", board_info_idx);
	// 카테고리별 메인 화면 이름 가져오기
	String board_idx_name = boardService.getBoard(board_info_idx);
	mo.addAttribute("board_idx_name", board_idx_name);
	// 카테고리 별 게시글 목록 가져오기
	List<Content> Content = boardService.getContent(board_info_idx);
	mo.addAttribute("Content", Content);

	return "board/main";
}

BoardService을 자동주입 받아, topmenu.jsp에서 파라미터로 넘겨준 카테고리 번호를 이용해 카테고리별 메인 화면 이름을 가져오는 getBoard(board_info_idx), getContent(board_info_idx) 메서드를 사용한다.

그 후 Model 객체에 각각의 메서드들이 반환한 데이터를 저장한 String과 List를 addAttribute를 이용해 "board_idx_name"과 "Content" 로 이름을 설정 한 뒤에 board/main 페이지로 요청해준다.

( 해당 메서드는 아래에서 설명 )


topmenu.jsp 파일 수정 - @RequestParam("board_info_idx") int board_info_idx

<ul class="navbar-nav">
	<c:forEach var='obj' items='${topmenuList }'>
		<li class="nav-item">
			<a href="${root }board/main?board_info_idx=${obj.board_info_idx}" class="nav-link">${obj.board_info_name }</a>
		</li>
	</c:forEach>
</ul>

BoardController에서 파라미터로 board_info_idx를 사용하기 위해서 topmenu.jsp 에서 url에 파라미터로 board_info_idx를 담아 보내줘야 한다.


/board/main.jsp

<h4 class="card-title">${board_idx_name}</h4>
<table class="table table-hover" id='board_list'>
	<thead>
		<tr>
			<th class="text-center d-none d-md-table-cell">글번호</th>
			<th class="w-50">제목</th>
			<th class="text-center d-none d-md-table-cell">작성자</th>
			<th class="text-center d-none d-md-table-cell">작성날짜</th>
		</tr>
	</thead>
	<tbody>
		<c:forEach var="content" items="${Content }">
			<tr>
				<td class="text-center d-none d-md-table-cell">${content.content_idx }</td>
				<td><a href="${root }board/read?board_info_idx=${board_info_idx}&content_idx=${content.content_idx}">
					${content.content_subject } </a></td>
				<td class="text-center d-none d-md-table-cell">${content.content_writer_name }</td>
				<td class="text-center d-none d-md-table-cell">${content.content_date }</td>
			</tr>
		</c:forEach>
	</tbody>
</table>

${board_idx_name}로 BoardController에서 모델에 저장한 카테고리 이름 (board_idx_name) 을 가져와 topmenu에서 

카테고리를 클릭하여 url을 요청 받는 경우 해당 정보를 DB에서 가져와 아래의 사진처럼 출력해준다.


게시글 목록

<c:forEach var="content" items="${Content }">
	<tr>
		<td class="text-center d-none d-md-table-cell">${content.content_idx }</td>
		<td><a href="${root }board/read?board_info_idx=${board_info_idx}&content_idx=${content.content_idx}">
			${content.content_subject } </a></td>
		<td class="text-center d-none d-md-table-cell">${content.content_writer_name }</td>
		<td class="text-center d-none d-md-table-cell">${content.content_date }</td>
	</tr>
</c:forEach>

BoardController 에서 getContent 메서드의 반환값인 List<Content> [모든 게시글 정보] 를 <c:foreach>태그를 사용해

테이블에 모든 게시글들을 번호가 내림차순이 되도록 아래 사진처럼 출력된다.


getBoard

BoardController에서 /board/main 에 대한 매핑 과정에서 BoardService를 주입받아 호출한 메서드
반환되는 데이터가 카테고리 이름

BoardMapper

@Select("select board_info_name from board_info_table where board_info_idx=#{board_info_idx}")
String getBoard(int board_info_idx);

topmenu에서 /board/main으로 이동할 때 board_info_idx의 값을 파라미터에 담아 요청을 보냈을 때, 데이터 베이스에서  해당 board_info_idx에 맞는 board_info_name을 반환한다 (String)


BoardDao

BoardMapper 자동 주입 후 함수 재정의

public String getBoard(int board_info_idx) {
		
	return boardMapper.getBoard(board_info_idx);
}

BoardService

BoardDao 자동주입 후 메서드 호출

public String getBoard(int board_info_idx) {
		
	return boardDao.getBoard(board_info_idx);
}

게시글 상세보기

게시글을 작성 후 해당 게시글에 대한 정보를 상세정보 페이지에서 볼 수 있도록 DB에서 게시글 정보를 가져오는 작업을 해주어야한다.


read.jsp

<div class="form-group">
   <label for="board_writer_name">작성자</label>
   <input type="text" id="board_writer_name" name="board_writer_name" class="form-control"
      value="${readBean.content_writer_name }" disabled="disabled" />
</div>
<div class="form-group">
   <label for="board_date">작성날짜</label>
   <input type="text" id="board_date" name="board_date" class="form-control" value="${readBean.content_date }"
      disabled="disabled" />
</div>
<div class="form-group">
   <label for="board_subject">제목</label>
   <input type="text" id="board_subject" name="board_subject" class="form-control" value="${readBean.content_subject }"
      disabled="disabled" />
</div>
<div class="form-group">
   <label for="board_content">내용</label>
   <textarea id="board_content" name="board_content" class="form-control" rows="10" style="resize:none"
      disabled="disabled">${readBean.content_text }</textarea>
</div>

<div class="form-group">
   <div class="text-right">
      <a href="${root }board/main?board_info_idx=${board_info_idx}" class="btn btn-primary">목록보기</a>
      <a href="${root }board/modify" class="btn btn-info">수정하기</a>
      <a href="${root }board/delete" class="btn btn-danger">삭제하기</a>
   </div>
</div>

BoardMapper에서 해당 데이터들을 가져오는 쿼리문을 작성하려 한다.
우선 작성자 이름, 글 제목, 글 내용, 작성일의 4가지를 가지고 와야 한다. 이 데이터들을 가지고 오기 위해서는 
content 테이블과 user_info 테이블을 조인해 가져오는 방법을 사용해야 한다.

오라클에서 해당 쿼리문을 작성해 출력해보면

content_idx (게시글 번호)를 기준으로 해당 데이터들을 가져오는 모습을 볼 수 있다.


BoardMapper

content_idx를 기준으로 불러오기 위해서 #{content_idx}로 쿼리문에 받아온 변수를 넣어준다.
해당 게시글 번호에 해당하는 게시글 정보와 게시글 테이블에서 FK로 설정한 회원 번호를 찾아 회원 이름을 찾아온다.

회원 이름을 Content DTO에 반환받기 위해서 "content_writer_name"를 DTO에 필드변수로 선언해 주어야한다.


BoardDao

Mapper에서 정의한 쿼리를 다시 재정의하여 쿼리문 실행


BoardService

Dao에서 재정의된 쿼리문의 반환값을 컨트롤러 매핑 시 사용한다.
게시글의 모든 정보이므로 반환형은 Content로 지정한다.


BoardController

/board/main.jsp / 게시글 제목 코드 : 링크에 파라미터를 담아 보내기

메인페이지에서 파라미터로 설정한 board_info_idx (카테고리 번호), content_idx (게시글 번호)를 받아와 read 페이지 요청을 하여 BoardController에서 매핑시 사용 할 수 있도록 한다.

board_info_idx (카테고리 번호)는 Model객체에 "board_info_idx"로 이름을 지정해 저장해주고,

content_idx(게시글 번호)는 쿼리문에 사용하기위해 Service의 메서드 호출 후 변수로 사용해 반환형이 Content 객체 readBean에 저장 후 이 객체를 Model 객체에 "readBean" 이름으로 저장한다.


read.jsp

<div class="form-group">
	<label for="board_writer_name">작성자</label>
	<input type="text" id="board_writer_name" name="board_writer_name" class="form-control" value="${readBean.content_writer_name }" disabled="disabled"/>
</div>
<div class="form-group">
	<label for="board_date">작성날짜</label>
	<input type="text" id="board_date" name="board_date" class="form-control" value="${readBean.content_date }" disabled="disabled"/>
</div>
<div class="form-group">
	<label for="board_subject">제목</label>
	<input type="text" id="board_subject" name="board_subject" class="form-control" value="${readBean.content_subject }" disabled="disabled"/>
</div>
<div class="form-group">
	<label for="board_content">내용</label>
	<textarea id="board_content" name="board_content" class="form-control" rows="10" style="resize:none" disabled="disabled">${readBean.content_text }</textarea>
</div>

작성자, 작성날짜, 제목, 내용의 인풋값을 매핑 과정에서 넘겨준 Model객체를 불러와 해당 위치에 맞게 데이터를 입력한다.

이제 게시글의 상세정보가 제대로 나오는지 확인해보면 아래의 사진처럼 나오는 모습을 볼 수 있다.

제목 1 클릭 시 게시글 상세 화면


게시글 수정 및 삭제 시 작성자인지 여부 판단

게시글 작성자가 아닌 경우에 게시글에 대해서 수정 및 삭제에 접근하지 못하게 하기

로그인 시 SessionScope로 세션영역에 User loginBean으로 유저의 정보를 저장한다.

그리고 게시글을 작성하는 경우 게시글 작성자의 정보, 게시글의 정보가 Content_table에 저장된다.

만약 작성자가 아닌 유저가 타인의 게시글을 수정하려고 하는 경우를 막아야 한다.
이를 사용하기 위해서는 수정 페이지인 modify와 삭제 페이지인 delete로 매핑 이전에 인터셉터를 통해 접근을 막아야한다.


BoardMapper

@Select("select ut.user_name as content_writer_name, to_char(ct.content_date,'YYYY-MM-DD') as content_date, "
		+ "ct.content_subject, ct.content_text, ct.content_writer_idx from content_table ct, "
		+ "user_info_table ut where ct.content_writer_idx = ut.user_idx and content_idx=${content_idx}")
Content ContentInfo(int content_idx);

유저가 접근하는 게시물의 번호를 가져와 데이터 베이스에서 해당 게시물의 모든 정보를 가져와 Content 객체에 저장하는 쿼리문을 정의한다.


WriteInterceptor

package co.ky.sol.interceptor;

public class WriterInterceptor implements HandlerInterceptor {

	// user_idx, content_writer_idx 비교

	private BoardService boardService;
	private User loginBean;

	public WriterInterceptor(BoardService boardService, User loginBean) {
		this.boardService = boardService;
		this.loginBean = loginBean;
	}

	// int형인 content_wwriter_idx를 reqeust.getParameter로 가져온다.
	// 그 후 BoardService에서 게시글 번호의 정보를 가져오는 ContentInfo메서드를 사용한다.
	@Override
	public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
			throws Exception {

		int con_idx = Integer.parseInt(request.getParameter("content_idx"));
		Content content = boardService.ContentInfo(con_idx);

		// 작성자와 로그인한 사람이 다르면 글쓰기 수정, 글쓰기 삭제 url을 들어갈 수 없다.
		if (loginBean.getUser_idx() != content.getContent_writer_idx()) {

			String str = request.getContextPath();
			response.sendRedirect(str + "/board/not_write");
			return false;
		}
		return true;
	}
}

BoardMapper에서 정의한 ContentInfo 메서드를 통해 Content 객체에 저장하고, 세션에 등록된 로그인 정보 중 회원번호와 데이터 베이스에서 반환받은 게시글의 정보 중 작성자 번호를 비교해, 만약 다른 번호라면 /board/not_write로 페이지를 강제 이동시킨다.


not_write

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<c:set var='root' value='${pageContext.request.contextPath }/'/>
<script>
	alert('접근이 잘못되었습니다')
	location.href='${root}main'
</script>

게시글 작성자가 아니라면 팝업창을 호출한 뒤 main 페이지로 이동시킨다.

유저가 게시글 수정, 삭제 버튼을 자신이 작성한 게시글에서만 볼 수 있도록 하기 위해서는 상세 페이지에서 <c:if>태그를 사용해 회원 번호와 게시글 작성자 번호를 비교해야한다.


read.jsp

<div class="form-group">
   <div class="text-right">
      <a href="${root }board/main?board_info_idx=${board_info_idx}" class="btn btn-primary">목록보기</a>
      <c:if test="${loginBean.getUser_name() ==  readBean.getContent_writer_name()}">
         <a href="${root }board/modify"
            class="btn btn-info">수정하기</a>
         <a href="${root }board/delete"
            class="btn btn-danger">삭제하기</a>
      </c:if>
   </div>
</div>

해당 작업을 완료하면 아래와 같이 회원 정보와 다른 게시글을 들어간 경우 목록보기만 뜨는것을 볼 수 있다.

Comments