🔥 게시글 삭제 🔥
⌨️ boardDetailView.jsp
👉🏻 로그인 한 유저와 글을 쓴 유저가 일치할 때만 수정/삭제 버튼이 보이게끔 조건 설정 및 링크 걸기
👉🏻 수정/삭제 많이 해 봤고 링크 뻔하니까 함께 걸어 봄 ^^*
<c:if test="${ loginUser.userId eq b.boardWriter }">
<div align="center">
<!-- 수정하기, 삭제하기 버튼은 이 글이 본인이 작성한 글일 경우에만 보여져야 함 -->
<a class="btn btn-primary" href="updateForm.bo?bno=${ b.boardNo }">수정하기</a>
<a class="btn btn-danger" href="delete.bo?bno=${ b.boardNo }">삭제하기</a>
</div>
<br><br>
</c:if>
📍 그런데 여기서 잠깐! 코딩 좀 할 줄 아는 놈이 나쁜 맘 먹고 아래 코드를 쳐 버린다면?
http://localhost:8006/spring/delete.bo?bno=100
boardNo: 100의 글이 실제로 삭제될 것! 😱
즉, 해킹의 위험에서 벗어나기 위해 쿼리스트링이 노출되지 않도록 하고 싶음!
💻 boardDetailView.jsp
<h2>게시글 상세보기</h2>
<br>
<a class="btn btn-secondary" style="float:right;" href="list.bo">목록으로</a>
<br><br>
<table id="contentArea" algin="center" class="table">
<tr>
<th width="100">제목</th>
<!-- 여기서 잘못 넣어 주면 getter 오류 남! -->
<td colspan="3">${ b.boardTitle }</td>
</tr>
<tr>
<th>작성자</th>
<td>${ b.boardWriter }</td>
<th>작성일</th>
<td>${ b.createDate }</td>
</tr>
<tr>
<th>첨부파일</th>
<td colspan="3">
<c:choose>
<c:when test="${ empty b.originName }">
첨부파일이 없습니다.
</c:when>
<c:otherwise>
<!-- 그냥 download로만 적어도 다운로드는 가능하지만, 그렇게 되면 수정명으로 파일 다운로드되므로 원본 파일명으로 처리해 줄 것! -->
<a href="${ b.changeName }" download="${ b.originName }">${ b.originName }</a>
</c:otherwise>
</c:choose>
</td>
</tr>
<tr>
<th>내용</th>
<td colspan="3"></td>
</tr>
<tr>
<td colspan="4"><p style="height:150px;">${ b.boardContent }</p></td>
</tr>
</table>
<br>
<c:if test="${ loginUser.userId eq b.boardWriter }">
<div align="center">
<!-- 수정하기, 삭제하기 버튼은 이 글이 본인이 작성한 글일 경우에만 보여져야 함 -->
<a class="btn btn-primary" onclick="postFormSubmit(1);">수정하기</a>
<a class="btn btn-danger" onclick="postFormSubmit(2);">삭제하기</a>
</div>
<br><br>
<!-- action 속성 비워 놓고 아래 script에서 action 속성만 바꿔치기 하는 게 포인트! -->
<form id="postForm" action="" method="post">
<input type="hidden" name="bno" value="${ b.boardNo }">
<!-- input 태그의 submit 버튼도 만들지 않음! -->
</form>
<script>
function postFormSubmit(num) {
// action 속성값을 부여 후 연이어서 곧바로 submit 시키기
if(num == 1) { // 수정하기 버튼 클릭 시 num == 1 : updateForm.bo
// location.href="updateForm.bo"; // get방식이기 때문에 url 노출됨! 우리의 목적에 적합하지 않음
$("#postForm").attr("action", "updateForm.bo").submit();
} else { // 삭제하기 버튼 클릭 시 num == 2 : delete.bo
$("#postForm").attr("action", "delete.bo").submit();
// 폼 태그 선택, action 속성을 바꾸고, 메소드 체이닝을 통해 submit
}
}
</script>
</c:if>
이제 더 이상 url에 게시글 번호가 노출되지 않음!
보안 풀충 완료 🔋
💻 BoardController
@RequestMapping("delete.bo")
public String deleteBoard(int bno, String filePath, HttpSession session, Model model) {
// System.out.println(bno);
int result = boardService.deleteBoard(bno);
if(result > 0) { // 게시글 삭제 성공
// 첨부파일이 있었을 경우 => 수정명 찍힘
// 첨부파일이 없었을 경우 => 빈 문자열 찍힘
// filePath에는 해당 게시글의 수정파일명이 들어 있음, 빈문자열이 아니라면 첨부파일이 있었던 경우일 것!
if(!filePath.equals("")) { // 첨부파일이 있었을 경우
String realPath = session.getServletContext().getRealPath(filePath);
new File(realPath).delete();
}
// 게시판 리스트 페이지 url 재요청
session.setAttribute("alertMsg", "성공적으로 게시글이 삭제되었습니다.");
return "redirect:/list.bo";
} else { // 게시글 삭제 실패
model.addAttribute("errorMsg", "게시글 삭제 실패");
return "common/errorPage";
}
}
💻 BoardServiceImpl
@Override
public int deleteBoard(int boardNo) {
return boardDao.deleteBoard(sqlSession, boardNo);
}
💻 BoardDao
public int deleteBoard(SqlSessionTemplate sqlSession, int boardNo) {
return sqlSession.update("boardMapper.deleteBoard", boardNo);
}
💻 board-mapper.xml
<update id="deleteBoard" parameterType="_int">
UPDATE BOARD
SET STATUS = 'N'
WHERE BOARD_NO = #{boardNo}
</update>
👉🏻 여기 17번째의 게시글이 있습니다
👉🏻 삭제하기 버튼 클릭 시 alert창이 잘 뜸
👉🏻 삭제 성공 시 list.bo로 url를 연결해 주었기 때문에 리스트가 다시 보이고, 삭제된 17번은 보이지 않게 됨!
👉🏻 STS의 uploadFiles 폴더를 refresh 했을 때 첨부파일이 보이지 않게 됨!
👉🏻 STS의 uploadFiles 폴더를 refresh 했을 때 첨부파일이 보이지 않게 됨!
🔥 게시글 수정 전 해당 게시글의 정보가 담긴 수정 폼 띄우기 🔥
⌨️ boardUpdateForm.jsp 생성 후 하드코딩
👉🏻 미리 만들어진 폼에서 head, body 영역 붙여 넣기
👉🏻 head의 3가지 링크는 include 할 header와 중복이기 때문에 삭제
👉🏻 head의 style 태그 중 content, innerOuter 역시 header와 중복이기 때문에 삭제
👉🏻 header, footer.jsp include 하기
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Document</title>
<style>
#updateForm>table {width:100%;}
#updateForm>table * {margin:5px;}
</style>
</head>
<body>
<jsp:include page="../common/header.jsp" />
<div class="content">
<br><br>
<div class="innerOuter">
<h2>게시글 수정하기</h2>
<br>
<form id="updateForm" method="post" action="" enctype="">
<table algin="center">
<tr>
<th><label for="title">제목</label></th>
<td><input type="text" id="title" class="form-control" value="게시판제목임ㅋㅋ" name="" required></td>
</tr>
<tr>
<th><label for="writer">작성자</label></th>
<td><input type="text" id="writer" class="form-control" value="user01" name="" readonly></td>
</tr>
<tr>
<th><label for="upfile">첨부파일</label></th>
<td>
<input type="file" id="upfile" class="form-control-file border" name="">
현재 업로드된 파일 :
<a href="" download="">flower.jpg</a>
</td>
</tr>
<tr>
<th><label for="content">내용</label></th>
<td><textarea id="content" class="form-control" rows="10" style="resize:none;" name="" required>여긴내용쓰</textarea></td>
</tr>
</table>
<br>
<div align="center">
<button type="submit" class="btn btn-primary">수정하기</button>
<button type="button" class="btn btn-danger" onclick="javascript:history.go(-1);">이전으로</button>
</div>
</form>
</div>
<br><br>
</div>
<jsp:include page="../common/footer.jsp" />
</body>
</html>
💻 boardUpdateForm.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Document</title>
<style>
#updateForm>table {width:100%;}
#updateForm>table * {margin:5px;}
</style>
</head>
<body>
<jsp:include page="../common/header.jsp" />
<div class="content">
<br><br>
<div class="innerOuter">
<h2>게시글 수정하기</h2>
<br>
<form id="updateForm" method="post" action="update.bo" enctype="multipart/form-data">
<table algin="center">
<tr>
<th><label for="title">제목</label></th>
<td><input type="text" id="title" class="form-control" value="${ b.boardTitle }" name="" required></td>
</tr>
<tr>
<th><label for="writer">작성자</label></th>
<td><input type="text" id="writer" class="form-control" value="${ b.boardWriter }" name="" readonly></td>
</tr>
<tr>
<th><label for="upfile">첨부파일</label></th>
<td>
<input type="file" id="upfile" class="form-control-file border" name="">
<!--
c:if 태그의 test 속성에는 originName, changeName 중 무엇이 들어가도 상관없음!
어차피 둘 중에 하나라도 있으면 첨부파일 있는 거고, 없으면 둘 다 없을 것이기 때문에!
-->
<c:if test="${ not empty b.originName }">
현재 업로드된 파일 :
<a href="${ b.changeName }" download="${ b.originName }">${ b.originName }</a>
</c:if>
</td>
</tr>
<tr>
<th><label for="content">내용</label></th>
<td><textarea id="content" class="form-control" rows="10" style="resize:none;" name="" required>${ b.boardContent }</textarea></td>
</tr>
</table>
<br>
<div align="center">
<button type="submit" class="btn btn-primary">수정하기</button>
<button type="button" class="btn btn-danger" onclick="javascript:history.go(-1);">이전으로</button>
</div>
</form>
</div>
<br><br>
</div>
<jsp:include page="../common/footer.jsp" />
</body>
</html>
💻 BoardController
👉🏻 DB에 가기 전에 먼저 해당 게시글 정보부터 조회해 옴 (= 조회한 내용으로 수정 폼을 띄우는 역할)
@RequestMapping("updateForm.bo")
public String updateForm(int bno, Model model) {
// 게시글 수정 페이지를 포워딩 하기 전에 우선적으로 해당 게시글 정보를 조회해 올 것
Board b = boardService.selectBoard(bno); // 기존의 상세보기 서비스를 재활용
model.addAttribute("b", b);
return "board/boardUpdateForm";
}
👉🏻 수정 폼에 내가 수정하고자 하는 글의 정보가 잘 담겨 있음
🔥 게시글 수정 🔥
💻 boardUpdateForm
👉🏻 name 속성 지정해 주기
👉🏻 input type="hidden"으로 boardNo, originName, changeName도 함께 넘겨 주기
👉🏻 이때, name 속성도 필드명과 맞춰 줌으로써 Controller에서 커맨드 객체 방식 이용할 것
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Document</title>
<style>
#updateForm>table {width:100%;}
#updateForm>table * {margin:5px;}
</style>
</head>
<body>
<jsp:include page="../common/header.jsp" />
<div class="content">
<br><br>
<div class="innerOuter">
<h2>게시글 수정하기</h2>
<br>
<form id="updateForm" method="post" action="update.bo" enctype="multipart/form-data">
<input type="hidden" name="boardNo" value="${ b.boardNo }">
<table algin="center">
<tr>
<th><label for="title">제목</label></th>
<td><input type="text" id="title" class="form-control" value="${ b.boardTitle }" name="boardTitle" required></td>
</tr>
<tr>
<th><label for="writer">작성자</label></th>
<td><input type="text" id="writer" class="form-control" value="${ b.boardWriter }" name="boardWriter" readonly></td>
</tr>
<tr>
<th><label for="upfile">첨부파일</label></th>
<td>
<input type="file" id="upfile" class="form-control-file border" name="reupfile">
<!--
c:if 태그의 test 속성에는 originName, changeName 중 무엇이 들어가도 상관없음!
어차피 둘 중에 하나라도 있으면 첨부파일 있는 거고, 없으면 둘 다 없을 것이기 때문에!
-->
<c:if test="${ not empty b.originName }">
현재 업로드된 파일 :
<a href="${ b.changeName }" download="${ b.originName }">${ b.originName }</a>
<!-- 수정을 하기 위해서는 만약 기존 파일이 있었다면 기존 파일에 대한 정보도 넘겨 줘야 함! -->
<input type="hidden" name="originName" value="${ b.originName }">
<input type="hidden" name="changeName" value="${ b.changeName }">
</c:if>
</td>
</tr>
<tr>
<th><label for="content">내용</label></th>
<td><textarea id="content" class="form-control" rows="10" style="resize:none;" name="boardContent" required>${ b.boardContent }</textarea></td>
</tr>
</table>
<br>
<div align="center">
<button type="submit" class="btn btn-primary">수정하기</button>
<button type="button" class="btn btn-danger" onclick="javascript:history.go(-1);">이전으로</button>
</div>
</form>
</div>
<br><br>
</div>
<jsp:include page="../common/footer.jsp" />
</body>
</html>
⌨️ BoardController
@RequestMapping("update.bo")
public void updateBoard(Board b, MultipartFile reupfile) {
if(!reupfile.getOriginalFilename().equals("")) { // 새로 넘어온 첨부파일이 있는 경우 == reupfile이 빈 문자열이 아니라면
// 1. 기존 첨부파일이 있었을 경우 => 기존 첨부파일 찾아서 삭제
System.out.println(b);
} else { // 새로 넘어온 첨부파일이 없는 경우
}
}
Board(boardNo=20, boardTitle=어라 내 첨부파일 게시글, boardWriter=admin, boardContent=어데 갔냐,
originName=0_스프링 환경구축.pdf, changeName=resources/uploadFiles/2022112215585925151.pdf, count=0, createDate=null, status=null)
✔️ 현재 콘솔에 찍힌 내용을 확인해 보면
👉🏻 b의 boardNo: 내가 수정하고자 하는 게시글의 번호 (WHERE절)
👉🏻 b의 boardTitle: 수정할 제목 (SET절)
👉🏻 b의 boardContent: 수정할 내용 (SET절)
👉🏻 b의 originName: 기존 첨부파일의 원본명
👉🏻 b의 changeName: 기존 첨부파일의 수정명
✔️ 첨부파일의 원본명, 수정명이 기존의 파일의 것으로 그대로 나옴!
👉🏻 b의 originName, changeName: jsp에서 input type="hidden"으로 넘겼기 때문에 기존의 파일 정보가 나옴
👉🏻 Controller에서 새로운 reupfile에 대한 정보로 바꿔치기 해 줘야 함!
현재 b에 무조건 담겨 있는 내용
👉🏻 boardNo, boardTitle, boardContent
추가적으로 고려해야 할 경우의 수
1. 새로 첨부된 파일 X, 기존 첨부파일 x
👉🏻 originName: null
👉🏻 changeName: null
2. 새로 첨부된 파일 X, 기존 첨부파일 O
👉🏻 originName: 기존 첨부파일 원본명
👉🏻 changeName: 기존 첨부파일 수정명 + 파일 경로
3. 새로 첨부된 파일 O, 기존 첨부파일 X
👉🏻 originName: 새로 첨부된 파일의 원본명
👉🏻 changeName: 새로 첨부된 파일의 수정명 + 파일 경로
4. 새로 첨부된 파일 O, 기존 첨부파일 O
👉🏻 기존 파일 삭제, 새로 전달된 파일을 서버에 업로드
👉🏻 originName: 새로 첨부된 파일의 원본명
👉🏻 changeName: 새로 첨부된 파일의 수정명 + 파일 경로
💻 BoardController
@RequestMapping("update.bo")
public String updateBoard(Board b, MultipartFile reupfile, HttpSession session, Model model) {
if(!reupfile.getOriginalFilename().equals("")) { // 새로 넘어온 첨부파일이 있는 경우 == reupfile이 빈 문자열이 아니라면
// 1. 기존 첨부파일이 있었을 경우 => 기존 첨부파일 찾아서 삭제
if(b.getOriginName() != null) {
String realPath = session.getServletContext().getRealPath(b.getChangeName());
new File(realPath).delete();
}
// 2. 새로 넘어온 첨부파일을 수정명으로 바꾸고 서버에 업로드 시키기
String changeName = saveFile(reupfile, session);
// 3. b 객체에 새로 넘어온 첨부파일에 대한 원본명, 수정 파일명 필드에 담기
b.setOriginName(reupfile.getOriginalFilename()); // 원본 파일명 덮어 씌움
b.setChangeName("resources/uploadFiles/" + changeName);
} // 새롭게 들어온 첨부파일이 없다면 굳이 파일의 원본/수정명을 바꿀 필요 없으므로 else문 없이 끝!
// Service단으로 b 보내기
int result = boardService.updateBoard(b);
if(result > 0) { // 게시글 수정 성공
session.setAttribute("alertMsg", "성공적으로 게시글이 수정되었습니다.");
// 게시글 상세보기 페이지로 url 재요청
return "redirect:/detail.bo?bno=" + b.getBoardNo();
} else { // 게시글 수정 실패
model.addAttribute("errorMsg", "게시글 수정 실패");
return "common/errorMsg";
}
💻 BoardService
👉🏻 매개변수를 int boardNo에서 Board b로 수정해야 함!
👉🏻 처음 로직을 생각했을 때는 boardNo만 바꿔 주면 될 줄 알았는데 내용 수정, 제목 수정, 첨부파일 삭제/추가 등 넘길 정보가 많으므로 Board 객체로 넘기는 게 맞다는 판단 때문!
// 게시글 수정 서비스
int updateBoard(Board b);
수정했더니 즉각 반응 오는 BoardServiceImpl의 매개변수도 바꿔 주자!
💻 BoardServiceImpl
@Override
public int updateBoard(Board b) {
return boardDao.updateBoard(sqlSession, b);
}
💻 BoardDao
public int updateBoard(SqlSessionTemplate sqlSession, Board b) {
return sqlSession.update("boardMapper.updateBoard", b);
}
💻 board-mapper.xml
<update id="updateBoard" parameterType="board">
UPDATE BOARD
SET BOARD_TITLE = #{boardTitle}
, BOARD_CONTENT = #{boardContent}
, ORIGIN_NAME = #{originName}
, CHANGE_NAME = #{changeName}
WHERE BOARD_NO = #{boardNo}
</update>
👉🏻 기존 게시글 상세보기에서 수정하기 버튼을 클릭해 보면
👉🏻 작성자 제외 내용들을 수정할 수 있음! 내용 수정 중... 첨부파일 첨부 중...
👉🏻 완료 후 수정하기 버튼 클릭
👉🏻 alert창 뜨고
👉🏻 수정이 완료된 모습이다