카테고리 없음

#83. 게시판(사진), 비로그인 접근제한, 작성자외 접근제한

열하나요 2023. 11. 3. 09:58

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";
    }
}