고래씌

[Spring] 8-5. 게시판 수정하기(+ 사진삭제, 사진수정, 사진추가) 본문

Server/Spring

[Spring] 8-5. 게시판 수정하기(+ 사진삭제, 사진수정, 사진추가)

고래씌 2024. 1. 26. 10:23

1. 일반게시판 수정

=> views/board/boardUpdateEnrollFrom.jsp 파일 생성
 
▶ boardUpdateEnrollFrom.jsp
- 사진이 없던 곳에 새롭게 추가된 경우 → INSERT
- 사진이 있던 곳에 새롭게 추가된 경우 → UPDATE
- 사진이 있던 곳에 삭제가 된 경우 → DELETE
- 사진이 있거나, 없던 곳에 그대로 없는 경우 → X

 
 
- 존재하고 있는 이미지가 제거된 경우, 해당 이미지의 pk(board_img_no)를 input태그에 저장시켜서 value값안에 있는 pk값으로 delete문의 조건식에 활용할 예정
                
- 만약 value값 안에 (1,3,5)라는 값이 담겨있다 → DELETE FROM BOARD_IMG WHERE BOARD_IMG_NO IN (1,3,5)

 
=> 이값이 BoardController.java로 넘어감
 
 

<%@ 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>Insert title here</title>
<style>
	img{
		width: 100px;
	}
</style>
</head>
<body>

	<jsp:include page="/WEB-INF/views/common/header.jsp" />
	
	<div class="content">
		<br><br>
		<div class="innerOuter">
			<h2>게시글 수정하기</h2>
			<br>
			<form actioin="${contextPath }/boarrd/update/${boardCode}/${boardNo}" id="enrollForm"
			method="post" enctype="multipart/form-data">
			<!-- 사진도 보내고 해야하기 때문에 multipart 사용 --> 
			<!-- 첨부파일은 ORIGIN_NAME과 CHANGE_NAME에 컬럼에 저장할 예정 -->
			
				<table align="center">
                    <tr>
                        <th>제목</th>
                        <td><input type="text" id="title" class="form-control" name="boardTitle" value="${board.boardTitle }" required></td>
                    </tr>
                    <tr>
                        <th>작성자</th>
                        <td>${board.boardWriter}</td>
                    </tr>
                    <c:if test="${boardCode ne 'T'}">   <!-- 사진게시판이 아닐때 -->
                        <tr>
                            <th>첨부파일</th>
                            <td><input type="file" id="upfile" class="form-control" name="upfile">${board.originName}
                            	<input type="hidden" name="originName" value="${board.originName}" />
                            	<input type="hidden" name="changeName" value="${board.changeName}" />
                            </td>
                        </tr>
                    </c:if>
                    <c:if test="${boardCode eq 'T' }">  <!-- 사진게시판 이면 -->
                    	<c:if test="${not empty board.imgList}">
                    		<c:forEach items="${board.imgList}" var="boardImg" varStatus="i"> <!-- varStatus : 현재 반복중인 상태값이 담긴 변수 -->
								<c:choose>
									<c:when test="${boardImg.imgLevel == 0}">
										<c:set var="img0" value="${contextPath}/resources/images/board/T/${boardImg.changeName}" />
										<c:set var="img0no" value="${boardImg.boardImgNo}" />
									</c:when>
									<c:when test="${boardImg.imgLevel == 1}">
										<c:set var="img1" value="${contextPath}/resources/images/board/T/${boardImg.changeName}" />
										<c:set var="img1no" value="${boardImg.boardImgNo}" />
									</c:when>
									<c:when test="${boardImg.imgLevel == 2}">
										<c:set var="img2" value="${contextPath}/resources/images/board/T/${boardImg.changeName}" />
										<c:set var="img2no" value="${boardImg.boardImgNo}" />
									</c:when>
									<c:when test="${boardImg.imgLevel == 3}">
										<c:set var="img3" value="${contextPath}/resources/images/board/T/${boardImg.changeName}" />
										<c:set var="img3no" value="${boardImg.boardImgNo}" />
									</c:when>
								</c:choose>
                    		</c:forEach>
                   		</c:if>
                    	<tr>
	                        <th><label  for="image">업로드 이미지1</label></th>
	                        <td>
	                        <img class="preview" src="${img0}">
	                        <input type="file" name="upfiles" class="form-control inputImage" accept="images/*" id="img1">
	                        <span class="delete-image" data-no="${img0no}">&times;</span>
                        </td>
	                    </tr>
	                    <tr>
	                        <th><label  for="image">업로드 이미지2</label></th>
	                        <td>
	                        <img class="preview" src="${img1}">
	                        <input type="file" name="upfiles" class="form-control inputImage" accept="images/*" id="img2">
	                        <span class="delete-image" data-no="${img1no}">&times;</span>
	                    </tr>
	                    <tr>
	                        <th><label  for="image">업로드 이미지3</label></th>
	                        <td>
	                        <img class="preview" src="${img2}">
	                        <input type="file" name="upfiles" class="form-control inputImage" accept="images/*" id="img3">
	                        <span class="delete-image" data-no="${img2no}">&times;</span>
	                        </td>
	                    </tr>
	                    <tr>
	                        <th><label  for="image">업로드 이미지4</label></th>
	                        <td>
	                        <img class="preview" src="${img3}">
	                        <input type="file" name="upfiles" class="form-control inputImage" accept="images/*" id="img4">
	                        <span class="delete-image" data-no="${img3no}">&times;</span>
	                        </td>
                    </tr>
                    </c:if>
                    <tr>
                        <th>내용</th>
                        <td>
                            <textarea id="content" style="resize:none;" rows="10" class="form-control"
                            name="boardContent" required="required">${board.boardContent}</textarea>
                        </td>
                    </tr>
                </table>
                
                <!-- 
                	존재하고 있는 이미지가 제거된 경우, 해당 이미지의 pk(board_img_no)를 input태그에 저장시켜서
                	value값안에 있는 pk값으로 delete문의 조건식에 활용할 예정
                	
                	만약 value값 안에 (1,3,5)라는 값이 담겨있다 → 
                	DELETE FROM BOARD_IMG
                	WHERE BOARD_IMG_NO IN (1,3,5)
                 -->
                 
                <input type="hidden" name="deleteList" id="deleteList" value="" />
                <div align="center">
                    <button type="submit" class="btn btn-primary">수정</button>
                    <button type="reset" class="btn btn-danger">취소</button>
                </div>
			</form>
		</div>
	</div>
	
	<!-- 사진 미리보기 -->
	<c:if test="${boardCode eq 'T'}"> <!-- 사진게시판이라면 -->
		<script>
			const inputImage = document.querySelectorAll('.inputImage'); // input type = file
			const preview = document.querySelectorAll('.preview'); // img
			const deleteImage = document.querySelectorAll('.delete-image'); // 삭제버튼들
			
			const deleteList = document.querySelector("#deleteList"); // hidden 태그
			const deleteSet = new Set();
			// 키값이 중복이 안됨. X 버튼이 눌릴때마다 추가추가 될건데 같은 값이 계속 추가되면 안되니 set으로 일차적으로 추가할 예정
			
			inputImage.forEach( function ( value, index  ){
				//현재 반복중인 file태그
				value.addEventListener('change', function(){
					if(this.files[0] != undefined){// 선택한 파일이 있는경우
						const reader = new FileReader(); // 선택된 파일을 읽을 객체 생성
						reader.readAsDataURL(this.files[0]);
						
						// reader가 파일을 다 읽어온 경우
						reader.onload = function(e) {
							preview[index].setAttribute("src", e.target.result);
						}
					}else{ // 파일이 선택되지 않았을때 (취소)
						preview[index].removeAttribute("src");
					}	
					
					let no = $(this).next().data('no')
					if(no != undefined && no) deleteSet.add(no); // span태그(x버튼) // 매개변수로 no 키값을 가져오고 있음
					deleteList.value = [...deleteSet];
				})
				
				// X 버튼을 클릭하였을때 사진 삭제하도록 설정
				deleteImage[index].addEventListener('click', function(){
					
					// 현재 미리보기가 존재하는 경우에 삭제처리되도록 수정
					if(preview[index].getAttribute("src") != "") {
						
						// 미리보기 삭제 + input태그 비워주기
						preview[index].removeAttribute("src");
						inputImage[index].value = "";
						
						// data.no
						let no = this.dataset.no;  // 이미지번호 or undefined
						
						if(no != undefined) deleteSet.add(no);  // 얻어온 no값을 저장함. no가 undefined가 아닐때만!
						
						// 저장되어있던 이미지를 "삭제"하고자할 때 사용
						deleteList.value = [...deleteSet] // 깊은복사 // 객체(set)를 배열로 변환
					}
				})
			})
		</script>
	</c:if>
	
	
	<jsp:include page="/WEB-INF/views/common/footer.jsp" />

</body>
</html>

 
 
▶ BoardController.java
=> 일반게시판 수정하기기능
=> 첨부파일 / 이미지 수정하기
+ 첨부파일이나 이미지 수정하는 경우, 웹서버에 저장한 파일 신경쓰지 말것. db정보만 바꿔주기
=> 게시글내용(boardContent)은 개행처리 된 상태이기 때문에 <br>태그를 /n로 변경해주는 작업이 필요
 
 
=> @RequestParam에 required 옵션을 주어 true일 경우는 필수, false일 경우는 필수가 아닌 것으로 설정 가능하다.
required = false일 때 요청 파라미터에 값이 없으면 null이 저장된다.
 
=> @RequestParam파라미터를 Map으로 조회하기도 가능
@RequestParam Map<String, Object> paramMap
Map에 key, value 형태로 파라미터와 파라미터의 값이 저장되게 된다.
만약 동일한 이름의 파라미터의 값이 2개 이상일 경우 MultiValueMap을 사용하면 된다.
 
 

☞ deleteList 에 이렇게 값이 담겨있는 것을 알수 있음. (log.info 결과)
☞ 우리는 이 결과값을 가지고 (deletList) SQL문에서 DELETE문에 활용할 예정(board-mapper.xml)

	// 일반게시판 수정하기기능
	// 첨부파일 / 이미지 수정하기
	// + 첨부파일이나 이미지 수정하는 경우, 웹서버에 저장한 파일 신경쓰지 말것. db정보만 바꿔주기
	@GetMapping("/update/{boardCode}/{boardNo}")
	public String updateBoard( 
			@PathVariable(value = "boardCode") String boardCode,  // C라는 값이 전달되면 String boardCode에는 C라는 값이 저장됨
			@PathVariable("boardNo") int boardNo,
			Model model) {
		
		// 게시글 정보(selectBoard), 첨부파일정보 조회후 함께 전달 
		BoardExt board = boardService.selectBoard(boardNo);
		
		// 게시글내용(boardContent)은 개행처리 된 상태이기 때문에 <br>태그를 /n로 변경해주는 작업이 필요
		board.setBoardContent(Utils.newLineClear(board.getBoardContent()));
		// => 엔터를 쳤다면 DB에서는 <br>로 보일텐데 우리는 개행문자 처리 함수(Utils.java에서)를 만들었기 때문에 이것을 이용 
		
		
		// PathVariable로 자동으로 request에 등록됨
		// model.addAttribute("boardCode", boardCode);
		// model.addAttribute("boardNo", boardNo);
		model.addAttribute("board", board);
		
		return "board/boardUpdateForm";
	}
	
	@PostMapping("/update/{boardCode}/{boardNo}")
	public String updateBoard2(
			@PathVariable("boardCode") String boardCode,
			@PathVariable("boardNo") int boardNo,
			Model model,
			Board board,
			// 리다이렉트할때 request스코프에 저장시킬 데이터를 담아주는 변수
			RedirectAttributes ra,  // 리다이렉트 할때만 활용해야한다!
			// 첨부파일
			@RequestParam(value="upfile", required=false) MultipartFile upfile,
			// 이미지파일들
			@RequestParam(value="upfiles", required=false) List<MultipartFile> upfiles,
			String deleteList // [1,2,3] 이런식으로 데이터가 담겨져있을 예정
			) {
		
		// 1) Board테이블 정보수정  => UPDATE
		board.setBoardNo(boardNo);  // update.jsp에는 input 태그에 boardNo가 없기 때문에 따로 넣어줘야함
		board.setBoardCd(boardCode); 
		log.info("board ?? {} deleteList ?? {}", board, deleteList); // boardNo, boardTitle, boardContent 값이 들어가있음
		

		int result = boardService.updateBoard(board, deleteList, upfile, upfiles);
		
		// 2) 이미지파일들 정보수정 => UPDATE, INSERT, DELETE
		// 사진이 없던 곳에 새롭게 추가된 경우 → INSERT
		// 사진이 있던 곳에 새롭게 추가된 경우 → UPDATE
		// 사진이 있던 곳에 삭제가 된 경우 → DELETE
		// 사진이 있거나, 없던 곳에 그대로 없는 경우 → X
		
		
		// 3) 작업결과에 따른 페이지 지정
		if(result > 0) {
			// 수정 성공시 list로 리다이렉트 되도록 설정(상대경로방식으로)
			// board/list/C/
			// board/update/C/6
			// 현재 디렉토리에서 상위디렉토리로 2번 올라가야함
			ra.addFlashAttribute("alertMsg", "게시글 수정 성공");   // 성공메시지
			return "redirect:../../list/" + boardCode;			
		}else {
			model.addAttribute("errorMsg", "게시글 수정 실패");
			return "common/errorPage";
		}
	}

 
 
 
▶ BoardService.java

	int updateBoard(Board board, String deleteList, MultipartFile upfile, List<MultipartFile> upfiles);

 
 
▶ BoardServiceImpl.java
=> 맨위에 application 등록

//	게시판 수정
    // 어떠한 에외가 발생한다면 롤백처리하겠다! 트랜잭션 처리를 함
	@Transactional(rollbackFor = {Exception.class})  
	@Override
	public int updateBoard(Board board, String deleteList, MultipartFile upfile, List<MultipartFile> upfiles) {
		
		board.setBoardContent(Utils.XSSHandling(board.getBoardContent()));
		board.setBoardContent(Utils.newLineHandling(board.getBoardContent()));
		board.setBoardTitle(Utils.XSSHandling(board.getBoardTitle()));
		
		// 게시글 업데이트
		int result = boardDao.updateBoard(board);
		
		// 이미지 및 첨부파일 등록
		String webPath = "/resources/images/board/" + board.getBoardCd() + "/";
		String serverFolderPath = application.getRealPath(webPath);
		
		// 게시글 등록이 성공적으로 완료되었을 때
		if(result > 0) {
			
			// 업로드된 이미지를 분류작업
			List<BoardImg> imgList = new ArrayList();
			
			if(upfiles != null) {  // 일반게시판, 자유게시판에서는 upfiles가 null임
				for(int i=0; i<upfiles.size(); i++) {
					if(!upfiles.get(i).isEmpty()) {
						
						String changeName = Utils.saveFile(upfiles.get(i), serverFolderPath);
						
						// BoardImg객체 생성후, 필요한 값을 추가해서 imgList에 추가
						BoardImg bi = new BoardImg();
						bi.setChangeName(changeName);
						bi.setOriginName(upfiles.get(i).getOriginalFilename());
						bi.setRefBno(board.getBoardNo());
						bi.setImgLevel(i);
						
						imgList.add(bi);
					}
				}
			}
			
			// x버튼을 눌러서 이미지를 삭제하고자 하는 경우
			if(deleteList != null && !deleteList.equals("")) {
				
				// 삭제하기 위해서는 board_img_no가 필요함
				result = boardDao.deleteBoardImg(deleteList);
			}
			
			// db에서 삭제처리 완료됐거나 혹은 게시판 업데이트 성공시
			if(result > 0) {
				// BoardImg객체 하나하나 업데이트
				
				for(BoardImg bi : imgList) {
					result = boardDao.updateBoardImg(bi);
					
					// result값은 1혹은 0으로 반환
					// result == 0 ? 실패 → 기존에 이미지가 없던 경우
					// result == 1 ? 성공 → 기존에 이미지가 있던 경우
					
					if(result == 0) {   // 기존에 이미지가 없으면 insert문 호출
						result = boardDao.insertBoardImg(bi); 
						// 기존에 있던 boardMapper 이용. changeName, OriginName, refBno, imgLevel 값이 다 있어서 문제없음
					}
				}
			}
		}
		
		
		return result;
	}

 
 
 
▶ BoardDao.java
1) 게시판 수정

int updateBoard(Board board);

 
2) 게시판 이미지 삭제

int deleteBoardImg(String deleteList);

 
3) 게시판 이미지 수정

int updateBoardImg(BoardImg bi);

 
 
 
▶ BoardDaoImpl.java
1) 게시판 수정

	@Override
	public int updateBoard(Board board) {
		return sqlSession.update("boardMapper.updateBoard", board);
	}

 
 
2) 게시판 이미지 삭제

	@Override
	public int deleteBoardImg(String deleteList) {
		return sqlSession.delete("boardMapper.deleteBoardImg", deleteList);
	}

 
 
 
3) 게시판 이미지 수정

	@Override
	public int updateBoardImg(BoardImg bi) {
		return sqlSession.update("boardMapper.updateBoardImg", bi);
	}

 
 
 
▶ board-mapper.xml
1) 게시판 수정

	<update id="updateBoard" parameterType="board">
		UPDATE BOARD
		SET BOARD_TITLE = #{boardTitle},
			BOARD_CONTENT = #{boardContent}
		WHERE BOARD_NO = #{boardNo}
	</update>

 
 
2) 게시판 이미지 삭제
 - #을 붙이면 preparedstatement 방식에 의해 '' 가 붙혀지는데 ''가 붙혀지면 삭제가 안됨.
- 그래서 $ 기호로 바꿔주어야한다!
- $ 기호는 statement 방식

	<delete id="deleteBoardImg" parameterType="string">
		DELETE FROM BOARD_IMG
		WHERE BOARD_IMG_NO IN (${deleteList})    
	</delete>

 
 
 
3) 게시판 이미지 수정

	<update id="updateBoardImg" parameterType="boardImg">
		UPDATE BOARD_IMG
		SET ORIGIN_NAME = #{originName},
			CHANGE_NAME = #{changeName}
		WHERE REF_BNO = #{refBno} AND IMG_LEVEL = #{imgLevel}
	</update>

 
 
 
▶ 결과