83-1. 사진
1. form태그의 속성값
enctype="multipart/form-data"
2. Controller에서 매개변수로 MultipartFile upfile를 가져옴
=> 여기서 변수명 upfile은 file타입의 input태그의 name속성값
** 라이브러리를 추가하지 않으면 같이 보내준 Board객체도 그렇고 upfile도 그렇고 null이 들어옴
3. 라이브러리 추가
1) 메이븐레파지토리에서 적정 라이브러리 (원래 cos썼는데 다중파일선택 업로드가 안돼서) commons-fileupload(1.3.3버전)를 pom.xml에 추가
2) apache commons io 추가 (2.6버전)
- apache를 쓰기 때문에 / (apache대신 NGINX도 많이 쓴다)
<!-- 4. 파일 업로드를 위한 라이브러리 -->
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.3.3</version>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.6</version>
</dependency>
4. 등록된 라이브러리 중에 Spring에게 관리를 요청할 클래스 빈등록
<!-- 파일 업로드 관련 빈 등록 -->
<bean class="org.springframework.web.multipart.commons.CommonsMultipartResolver" id="multipartResolver">
<property name="maxUploadSize" value="100000000"/>
<property name="maxInMemorySize" value="100000000" />
</bean>
5. 파일명을 날짜시간 + 랜덤값 숫자 5자리 (+ 확장자)로 바꿔준다.
먼저, 날짜시간을 뽑고
String currentTime = new SimpleDateFormat("yyyyMMddHHmmss").format(new Date());
랜덤숫자 5자리를 뽑고
int ranNum = (int)Math.random() * 90000 + 10000;
원래 파일의 확장자를 뽑아서 파일명을 새로 지정해준다.
MultipartFile에는 업로드된 파일의 정보가 들어가 있다.
(여러개 있을 경우 MultipartFile[] 배열로 들어옴)
String originName = upfile.getOriginalFilename();
String ext = originName.substring(originName.lastIndexOf("."));
String changeName = currentTime + ranNum + ext;
실제 저장할 경로를 뽑아서 바뀐 파일명으로 저장해준다.
String savePath = session.getServletContext().getRealPath("/resources/uploadFiles/");
try {
upfile.transferTo(new File(savePath + changeName));
} catch (IllegalStateException | IOException e) {
e.printStackTrace();
}
6. Controller로 입력된 값들(사진포함) 업로드
@RequestMapping("insert.bo")
public String insertBoard(Board b,
MultipartFile upfile, // 여러 개의 첨부파일을 전달 받을 시 MultipartFile[] 배열로 넘어옴
HttpSession session,
Model model) {
// System.out.println(b);
// System.out.println(upfile);
// 첨부파일이 있건 없건 무조건 객체가 생성!(차이점 filename필드에 원본명이 존재하는가 / ""인가)
// 전달된 파일이 존재할 경우! => 파일명 수정 후 서버에 업로드
if(!upfile.getOriginalFilename().equals("")) {
/*
// => 파일의 원본명, 서버의 업로드 할 경로 + 바뀐 이름을 b에 이어서 담기
// 파일명 수정 작업 후 서버에 업로드시키기("bono.jsp" => 20231103102055123456.jsp)
String originName = upfile.getOriginalFilename();
// "20231103102244"(년월일시분초)
String currentTime = new SimpleDateFormat("yyyyMMddHHmmss").format(new Date());
/*
* 자바 개발자
*
* == 자바를 이용해서 데이터 다루기
*
* 1. 정수데이터
* 2. 문자열데이터
* 3. 날짜데이터
*
* 4. JSON데이터
*
// 23432(5자리 랜덤값)
int ranNum = (int)Math.random() * 90000 + 10000;
// Random r = new Random(); Math.random()갈 필요없이 이걸 써도 되긴함
// 확장자
String ext = originName.substring(originName.lastIndexOf("."));
String changeName = currentTime + ranNum + ext;
String savePath = session.getServletContext().getRealPath("/resources/uploadFiles/");
try {
upfile.transferTo(new File(savePath + changeName));
} catch (IllegalStateException | IOException e) {
e.printStackTrace();
}
*/
// 공유해서 쓰거나 중복을 방지하기 위해 메소드로 빼서 사용
// saveFile(upfile, session); // 호출 시에 필요한 변수 같이 넘겨주기
// Board객체의 originName + changeName
b.setOriginName(upfile.getOriginalFilename());
b.setChangeName(saveFile(upfile, session)); // 바꿔준 이름이 메소드 안에 있으므로 return으로 값 가져오기
}
// 넘어온 첨부파일이 존재하지 않을 경우 b : 제목, 작성자, 내용
// 넘어온 첨부파일이 존재할 경우 b :제목, 작성자, 내용, 원본명, 저장경로 + 바뀐이름
if(boardService.insertBoard(b) > 0) { // 성공 => 게시글 목록을 보여주기!
session.setAttribute("alertMsg", "게시글 작성 성공~");
return "redirect:list.bo";
} else {
model.addAttribute("errorMsg", "게시글 작성 실패...");
return "common/errorPage";
}
}
// 파일 업로드 메소드
public String saveFile(MultipartFile upfile, HttpSession session) {
// 파일명 수정 작업 후 서버에 업로드시키기("bono.jsp" => 20231103102055123456.jsp)
String originName = upfile.getOriginalFilename();
// "20231103102244"(년월일시분초)
String currentTime = new SimpleDateFormat("yyyyMMddHHmmss").format(new Date());
// 23432(5자리 랜덤값)
int ranNum = (int)(Math.random() * 90000) + 10000;
// Random r = new Random(); Math.random()갈 필요없이 이걸 써도 되긴함
// 확장자
String ext = originName.substring(originName.lastIndexOf("."));
String changeName = currentTime + ranNum + ext;
String savePath = session.getServletContext().getRealPath("/resources/uploadFiles/");
try {
upfile.transferTo(new File(savePath + changeName));
} catch (IllegalStateException | IOException e) {
e.printStackTrace();
}
return "resources/uploadFiles/" + changeName;
}
여기서 return값을 "/resources/uploadFiles/" + changeName; 로 주면
83-2. 로그인한 사용자만 이용할 수 있는 페이지
조건처리를 매번 해주기는 번거롭다
1. 클래스 파일을 만든다.
2. HandlerInterceptorAdapter를 extends해준다. 이 때, (ctrl + shif+ s )현재 상속받을 수 있는 인터페이스와 클래스가 나오는데 체크하고 ok를 누르면 자동으로 extends해준다.
package com.kh.spring.common.interceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
// 까만줄 : 버전이 올라가면 없앨 예정이야~
public class LoginInterceptor extends HandlerInterceptorAdapter {
/*
* Interceptor(정확히 HandlerInterceptor)
*
* Controller가 호출되기 전(혹은 돌아갈 때도 가능), 실행된 후 가로채서 실행할 내용을 작성 가능
*
* preHandler(전처리) : 핸들러 호출 전 낚아챔
* postHandler(후처리) : 요청 처리 후 낚아챔
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
// true 리턴 시 => 기존 요청 호출대로 Handler를 정상 실행(Controller메소드 호출)
// false 리턴 시 => Controller 실행X
// 현재 요청을 보낸 사람이 로그인이 되어있을 경우 => Controller를 호출
HttpSession session = request.getSession();
if(session.getAttribute("loginUser") != null) {
return true;
} else {
session.setAttribute("alertMsg", "로그인 부탁드려요!");
response.sendRedirect(request.getContextPath());
return false;
}
}
}
3. servlet-contents에 servlet 설정정보를 넣어준다.
<!-- 로그인 인터셉터 -->
<interceptors>
<interceptor>
<mapping path="/myPage.me" />
<mapping path="/enrollForm.bo" />
<beans:bean class="com.kh.spring.common.interceptor.LoginInterceptor" id="loginInterceptor" />
</interceptor>
</interceptors>
83-3. 악성유저 접근제한
url을 수정하면 작성자가 아니더라도 아무나 게시글을 삭제하거나 수정할 수 있다.
a태는 get방식이라 누구나 수정이 가능하다.
<a class="btn btn-danger" href="delete.bo?bno=${ b.boardNo }">삭제하기</a>
url을 get방식이 아닌 post방식으로 바꿔준다.
1. form으로 method="post"방식으로 보내준다.
<c:if test="${ loginUser.userId eq b.boardWriter }">
<div align="center">
<a class="btn btn-primary" onclick="postFormSubmit(0)">수정하기</a>
<a class="btn btn-danger" onclick="postFormSubmit(1)">삭제하기</a>
</div>
</c:if>
<form action="" method="post" id="postForm">
<input type="hidden" name="bno" value="${ b.boardNo }">
</form>
2. JavaScript를 통해 조건에 따라 매핑값을 달리해준다.
<script>
function postFormSubmit(num){ // this로 보낼 수도 있으나, 또 this의 innerHTML이 수정하기냐 삭제하기냐 비교해야 함
if(num == 0){
// 수정하기 클릭 시
$('#postForm'.attr('action', 'updateForm.bo').submit();
}
else{
// 삭제하기 클릭 시
$('#postForm').attr('action', 'delete.bo').submit();
}
}
</script>
3. Controller에서 값을 받아 update해준다. (status = 'N')
/**
* deleteBoard : 사용자에게 게시글 번호를 전달 받아서 Board테이블에 STATUS컬럼의 값을 N으로 바꿔주는 메소드
*
* @param bno : 삭제 요청을 받은 게시글의 번호
* @param session : 응답 시 전달값을 담기 위한 HTTP타입 세션 객체
* @return : 반환될 View의 논리적인 경로
*/
@RequestMapping("delete.bo")
public String deleteBoard(int bno, HttpSession session) {
if(boardService.deleteBoard(bno) > 0) { // 삭제 성공
session.setAttribute("alertMsg", "삭제 성공~~");
// 지금 alertMsg의 remove를 session에서 해줬기 때문에 이렇게 쓰게 됨
// (remove의 scope을 안적었다면 모든 scope에서 지우기 때문에 굳이 session으로 안해줘도 됨)
return "redirect:list.bo";
} else {
session.setAttribute("errorMsg", "못지웠어요ㅠㅠ");
return "common/errorPage";
}
}
83-4. 게시글 삭제 중 첨부파일 삭제
DB를 갔다오는 것만으로도 비용이니, DB를 가지 않고 첨부파일을 삭제해보자.
1. 파일의 이름을 같이 보내주자
<form action="" method="post" id="postForm">
<input type="hidden" name="bno" value="${ b.boardNo }" />
<input type="hidden" name="filePath" value="${ b.changeName }" />
</form>
2. 첨부파일을 삭제해주자
게시글 삭제에 성공했다면 첨부파일도 삭제
if(!filePath.equals("")) {
// 기존에 존재하는 첨부파일을 삭제
// resources/xxxxx/xxxxx.jsp
new File(session.getServletContext().getRealPath(filePath)).delete();
}
83-5. 게시글 수정 시 첨부파일 유무
@RequsetMapping말고 @GetMapping이나 @PostMapping도 사용할 수 있음
=> 이렇게 사용하게 되면, mapping값을 동일하게 사용할 수 있음
@PostMapping("update.bo")
public String updateBoard(@ModelAttribute /* 가시성을 위해 작성할 수 있음 */ Board b,
MultipartFile reUpfile,
HttpSession session) {
/*
* Board b에 boardTitle, boardContent, boardWriter, + boardNo (hidden으로 넘겨줌)
* 새로 첨부파일은 reUpfile로 알 수 있지만, 기존 첨부파일은 b객체에 originName hidden으로 넘겨줌
* b객체에 changeName도 hidden으로 넘겨줌
*
* 이 네개는 무조건 들어있음!!!!!!!!!!!
*
*
* 1. 새로 첨부파일 X, 기존 첨부파일 X => origin : null
*
*
* 2. 새로 첨부파일 X, 기존 첨부파일 O => origin : 기존 첨부파일 이름, change : 기존 첨부파일 경로
*
*
* 3. 새로 첨부파일 O, 기존 첨부파일 X => origin : 새로운 첨부파일 이름, change : 새로운 첨부파일 경로
*
*
* 4. 새로 첨부파일 O, 기존 첨부파일 O => origin : 새로운 첨부파일 이름, change : 새로운 첨부파일 경로
*
*
*/
// 새로운 첨부파일을 첨부한 경우
if(!reUpfile.getOriginalFilename().equals("")) {
// 기존에 첨부파일이 존재했는지 체크 => 기존의 첨부파일 삭제
if(b.getOriginName() != null) {
new File(session.getServletContext().getRealPath(b.getChangeName())).delete();
}
// 새로 넘어온 첨부파일 서버에 업로드 시키기
// saveFile()
b.setOriginName(reUpfile.getOriginalFilename());
b.setChangeName(saveFile(reUpfile, session));
// b라는 Board 타입객체에 새로운 정보(원본파일명, 저장경로+바뀐이름) 담기
}
if(boardService.updateBoard(b) > 0) {
session.setAttribute("alertMsg", "변했어~~~~~~~");
return "redirect:detail.bo?bno=" + b.getBoardNo();
} else {
session.setAttribute("errorMsg", "아아아아아아아아ㅏㄱ");
return "common/errorPage";
}
}